diff --git a/.vscode/launch.json b/.vscode/launch.json
index 0d23ec678b..199a2f94e4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -68,7 +68,8 @@
"module": "uvicorn",
"cwd" : "${workspaceFolder}/src/python/LangChainAPI/app",
"env": {
- "PYTHONPATH": "d:/repos/solliance-foundationallm/src/python/LangChainAPI;d:/repos/solliance-foundationallm/src/python/PythonSDK;d:/repos/fllm/foundationallm/src/python/PythonSDK;"
+ "PYTHONPATH": "${workspaceFolder}/src/python/LangChainAPI;${workspaceFolder}/src/python/PythonSDK;d:/repos/fllm/foundationallm/src/python/PythonSDK;",
+ "FOUNDATIONALLM_CONTEXT": "DEBUG"
},
"args": ["main:app","--reload", "--port", "8765"],
"python": "${workspaceFolder}/src/python/LangChainAPI/env/Scripts/python.exe"
diff --git a/src/dotnet/Common/Constants/ResourceProviders/ResourceObjectIdPropertyNames.cs b/src/dotnet/Common/Constants/ResourceProviders/ResourceObjectIdPropertyNames.cs
new file mode 100644
index 0000000000..8dfe0ebed1
--- /dev/null
+++ b/src/dotnet/Common/Constants/ResourceProviders/ResourceObjectIdPropertyNames.cs
@@ -0,0 +1,28 @@
+namespace FoundationaLLM.Common.Constants.ResourceProviders
+{
+ ///
+ /// Contains constants of the resource property names.
+ ///
+ public static class ResourceObjectIdPropertyNames
+ {
+ ///
+ /// Object role.
+ ///
+ public const string ObjectRole = "object_role";
+
+ ///
+ /// Model parameters.
+ ///
+ public const string ModelParameters = "model_parameters";
+
+ ///
+ /// Text embedding model name.
+ ///
+ public const string TextEmbeddingModelName = "text_embedding_model_name";
+
+ ///
+ /// Text embedding model parameters.
+ ///
+ public const string TextEmbeddingModelParameters = "text_embedding_model_parameters";
+ }
+}
diff --git a/src/dotnet/Common/Constants/ResourceProviders/ResourceObjectIdPropertyValues.cs b/src/dotnet/Common/Constants/ResourceProviders/ResourceObjectIdPropertyValues.cs
new file mode 100644
index 0000000000..c581139f1e
--- /dev/null
+++ b/src/dotnet/Common/Constants/ResourceProviders/ResourceObjectIdPropertyValues.cs
@@ -0,0 +1,23 @@
+namespace FoundationaLLM.Common.Constants.ResourceProviders
+{
+ ///
+ /// Contains constants of the resource property values.
+ ///
+ public static class ResourceObjectIdPropertyValues
+ {
+ ///
+ /// Main model.
+ ///
+ public const string MainModel = "main_model";
+
+ ///
+ /// Main prompt.
+ ///
+ public const string MainPrompt = "main_prompt";
+
+ ///
+ /// Main indexing profile.
+ ///
+ public const string MainIndexingProfile = "main_indexing_profile";
+ }
+}
diff --git a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs
index 4be3f4f26e..55d486d859 100644
--- a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs
+++ b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentTool.cs
@@ -28,34 +28,10 @@ public class AgentTool
public required string PackageName { get; set; }
///
- /// Gets or sets a dictionary of AI model object identifiers.
+ /// Gets or sets a dictionary of resource objects.
///
- ///
- /// The key is a value that is well-known to the tool, and the value is the AI model object identifier.
- ///
- [JsonPropertyName("ai_model_object_ids")]
- public Dictionary AIModelObjectIds { get; set; } = [];
-
- ///
- /// Gets of sets a dictionary of API endpoint configuration object identifiers.
- ///
- ///
- /// The key is a value that is well-known to the tool, and the value is the API endpoint configuration object identifier.
- ///
- [JsonPropertyName("api_endpoint_configuration_object_ids")]
- public Dictionary APIEndpointConfigurationObjectIds { get; set; } = [];
-
- ///
- /// Gets or sets a dictionary of indexing profile object identifiers.
- ///
- [JsonPropertyName("indexing_profile_object_ids")]
- public Dictionary IndexingProfileObjectIds { get; set; } = [];
-
- ///
- /// Gets or sets a dictionary of text embedding model names.
- ///
- [JsonPropertyName("text_embedding_model_names")]
- public Dictionary TextEmbeddingModelNames { get; set; } = [];
+ [JsonPropertyName("resource_object_ids")]
+ public Dictionary ResourceObjectIds { get; set; } = [];
///
/// Gets or sets a dictionary of properties that are specific to the tool.
diff --git a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentWorkflows/AgentWorkflowBase.cs b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentWorkflows/AgentWorkflowBase.cs
index bf29677091..5d06455c92 100644
--- a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentWorkflows/AgentWorkflowBase.cs
+++ b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentWorkflows/AgentWorkflowBase.cs
@@ -1,4 +1,5 @@
-using System.Text.Json.Serialization;
+using FoundationaLLM.Common.Constants.ResourceProviders;
+using System.Text.Json.Serialization;
namespace FoundationaLLM.Common.Models.ResourceProviders.Agent.AgentWorkflows
{
@@ -18,29 +19,34 @@ public class AgentWorkflowBase
public virtual string? Type { get; set; }
///
- /// The workflow resource associated with the agent.
+ /// The name of the workflow.
///
- [JsonPropertyName("workflow_object_id")]
- public required string WorkflowObjectId { get; set; }
+ ///
+ /// This value is always derived from the property.
+ ///
+ [JsonPropertyName("workflow_name")]
+ public string? WorkflowName { get; set; }
///
- /// The name of the workflow resource associated with the agent.
+ /// The host of the workflow environment.
///
- [JsonPropertyName("workflow_name")]
- public required string WorkflowName { get; set; }
+ [JsonPropertyName("workflow_host")]
+ public string? WorkflowHost { get; set; }
///
- /// The collection of AI models available to the workflow.
- /// The well-known key "main-model" is used to specify the model for the main workflow.
+ /// Gets or sets a dictionary of resource objects.
///
- [JsonPropertyName("agent_workflow_ai_models")]
- public Dictionary AgentWorkflowAIModels { get; set; } = [];
+ [JsonPropertyName("resource_object_ids")]
+ public Dictionary ResourceObjectIds { get; set; } = [];
///
- /// The collection of prompt resources available to the workflow.
- /// The well-known key "main-prompt" is used to specify the prompt for the main workflow.
+ /// Gets the main AI model object identifier.
///
- [JsonPropertyName("prompt_object_ids")]
- public Dictionary PromptObjectIds { get; set; } = [];
+ [JsonIgnore]
+ public string? MainAIModelObjectId =>
+ ResourceObjectIds.Values
+ .FirstOrDefault(
+ roid => roid.HasObjectRole(ResourceObjectIdPropertyValues.MainModel))
+ ?.ObjectId;
}
}
diff --git a/src/dotnet/Common/Models/ResourceProviders/ResourceObjectIdProperties.cs b/src/dotnet/Common/Models/ResourceProviders/ResourceObjectIdProperties.cs
new file mode 100644
index 0000000000..e3aa8d635c
--- /dev/null
+++ b/src/dotnet/Common/Models/ResourceProviders/ResourceObjectIdProperties.cs
@@ -0,0 +1,33 @@
+using FoundationaLLM.Common.Constants.ResourceProviders;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace FoundationaLLM.Common.Models.ResourceProviders
+{
+ ///
+ /// Defines the properties of a resource.
+ ///
+ public class ResourceObjectIdProperties
+ {
+ ///
+ /// The unique identifier of the resource.
+ ///
+ [JsonPropertyName("object_id")]
+ public required string ObjectId { get; set; }
+
+ ///
+ /// Gets or sets a dictionary of properties.
+ ///
+ [JsonPropertyName("properties")]
+ public Dictionary Properties { get; set; } = [];
+
+ ///
+ /// Indicates whether the resource has the specified object role.
+ ///
+ /// The object role being searched.
+ /// if the object role is present, otherwise.
+ public bool HasObjectRole(string role) =>
+ Properties.TryGetValue(ResourceObjectIdPropertyNames.ObjectRole, out var objectRole)
+ && ((JsonElement)objectRole).GetString() == role;
+ }
+}
diff --git a/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs b/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs
index a646640ae1..74b96cab2a 100644
--- a/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs
+++ b/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs
@@ -387,6 +387,55 @@ public bool MatchesResourceTypes(ResourcePath other)
return true;
}
+ ///
+ /// Parses a resource path.
+ ///
+ /// The resource path to be parsed.
+ /// A object containing the parsed resource path.
+ public static ResourcePath GetResourcePath(string resourcePath)
+ {
+ TryParseResourceProvider(resourcePath, out var resourceProvider);
+
+ var allowedResourceProviders = ImmutableList.Empty;
+ var allowedResourceTypes = new Dictionary();
+
+ if (resourceProvider != null)
+ {
+ allowedResourceProviders = allowedResourceProviders.Add(resourceProvider);
+ allowedResourceTypes = GetAllowedResourceTypes(resourceProvider);
+ }
+
+ if (!TryParse(
+ resourcePath,
+ allowedResourceProviders,
+ allowedResourceTypes,
+ false,
+ out ResourcePath? parsedResourcePath))
+ throw new AuthorizationException($"The resource path [{resourcePath}] is invalid.");
+
+ return parsedResourcePath!;
+ }
+
+ ///
+ /// Retrieves the allowed resource types for a specified resource provider.
+ ///
+ /// The name of the resource provider.
+ public static Dictionary GetAllowedResourceTypes(string resourceProvider) =>
+ resourceProvider switch
+ {
+ ResourceProviderNames.FoundationaLLM_Agent => AgentResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_DataSource => DataSourceResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_Prompt => PromptResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_Vectorization => VectorizationResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_Configuration => ConfigurationResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_Attachment => AttachmentResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_Authorization => AuthorizationResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_AIModel => AIModelResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_AzureOpenAI => AzureOpenAIResourceProviderMetadata.AllowedResourceTypes,
+ ResourceProviderNames.FoundationaLLM_Conversation => ConversationResourceProviderMetadata.AllowedResourceTypes,
+ _ => []
+ };
+
private void ParseResourcePath(
string resourcePath,
ImmutableList allowedResourceProviders,
diff --git a/src/dotnet/Common/Utils/ResourcePathUtils.cs b/src/dotnet/Common/Utils/ResourcePathUtils.cs
index 0e6250d48a..a825404e9b 100644
--- a/src/dotnet/Common/Utils/ResourcePathUtils.cs
+++ b/src/dotnet/Common/Utils/ResourcePathUtils.cs
@@ -83,52 +83,12 @@ private static ResourcePath ParseInternal(
|| !allowedInstanceIds.Contains(instanceId!))
throw new AuthorizationException("The resource path does not contain a valid FoundationaLLM instance identifier.");
- var parsedResourcePath = GetResourcePath(resourcePath);
+ var parsedResourcePath = ResourcePath.GetResourcePath(resourcePath);
if (!allowRootPath && parsedResourcePath.IsRootPath)
throw new AuthorizationException("A root resource path is not allowed in this context.");
return parsedResourcePath;
}
-
- private static ResourcePath GetResourcePath(string resourcePath)
- {
- ResourcePath.TryParseResourceProvider(resourcePath, out var resourceProvider);
-
- var allowedResourceProviders = ImmutableList.Empty;
- var allowedResourceTypes = new Dictionary();
-
- if (resourceProvider != null)
- {
- allowedResourceProviders = allowedResourceProviders.Add(resourceProvider);
- allowedResourceTypes = GetAllowedResourceTypes(resourceProvider);
- }
-
- if (!ResourcePath.TryParse(
- resourcePath,
- allowedResourceProviders,
- allowedResourceTypes,
- false,
- out ResourcePath? parsedResourcePath))
- throw new AuthorizationException($"The resource path [{resourcePath}] is invalid.");
-
- return parsedResourcePath!;
- }
-
- private static Dictionary GetAllowedResourceTypes(string resourceProvider) =>
- resourceProvider switch
- {
- ResourceProviderNames.FoundationaLLM_Agent => AgentResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_DataSource => DataSourceResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_Prompt => PromptResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_Vectorization => VectorizationResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_Configuration => ConfigurationResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_Attachment => AttachmentResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_Authorization => AuthorizationResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_AIModel => AIModelResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_AzureOpenAI => AzureOpenAIResourceProviderMetadata.AllowedResourceTypes,
- ResourceProviderNames.FoundationaLLM_Conversation => ConversationResourceProviderMetadata.AllowedResourceTypes,
- _ => []
- };
}
}
diff --git a/src/dotnet/Orchestration/Orchestration/ExplodedObjectsManager.cs b/src/dotnet/Orchestration/Orchestration/ExplodedObjectsManager.cs
new file mode 100644
index 0000000000..160e518889
--- /dev/null
+++ b/src/dotnet/Orchestration/Orchestration/ExplodedObjectsManager.cs
@@ -0,0 +1,66 @@
+using ZstdSharp.Unsafe;
+
+namespace FoundationaLLM.Orchestration.Core.Orchestration
+{
+ ///
+ /// Manages the exploded objects dictionary ensuring consistency and integrity.
+ ///
+ public class ExplodedObjectsManager
+ {
+ private readonly Dictionary _explodedObjects = [];
+
+ ///
+ /// Adds a new key value pair to the exploded objects dictionary.
+ ///
+ /// The key of the object to add to the dictionary.
+ /// The object to add to the dictionary.
+ /// if the value was added successfully, otherwise.
+ ///
+ /// The first attempt to add an object always wins.
+ /// This means that if the key already exists in the dictionary, the add operation will have no effect and it will not generate an exception either.
+ ///
+ public bool TryAdd(string key, object value)
+ {
+ if (_explodedObjects.ContainsKey(key))
+ return false;
+
+ _explodedObjects.Add(key, value);
+ return true;
+ }
+
+ ///
+ /// Indicates whether the exploded objects dictionary contains the specified key.
+ ///
+ /// The key being searched for.
+ /// if the key is present in the dictionary (even if the associated value is null), otherwise.
+ public bool HasKey(string key) =>
+ _explodedObjects.ContainsKey(key);
+
+ ///
+ /// Tries to get the value associated with the specified key.
+ ///
+ /// The type of the value associated with the key.
+ /// The key being searched for.
+ /// The value being searched for.
+ /// The typed object associated with the specified key.
+ public bool TryGet(string key, out T? value) where T : class
+ {
+ value = default(T);
+
+ if (_explodedObjects.TryGetValue(key, out var obj))
+ {
+ value = obj as T;
+ return value != null;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the exploded objects dictionary.
+ ///
+ /// A shallow copy of the internal exploded objects dictionary. This only prevents unguarded changes to the key-value pairs but not to the values themselves.
+ public Dictionary GetExplodedObjects() =>
+ new(_explodedObjects);
+ }
+}
diff --git a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs
index e955da90ed..c2e8562326 100644
--- a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs
+++ b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs
@@ -1,3 +1,5 @@
+using AngleSharp.Common;
+using ClosedXML.Excel;
using FoundationaLLM.Common.Constants;
using FoundationaLLM.Common.Constants.Agents;
using FoundationaLLM.Common.Constants.ResourceProviders;
@@ -20,6 +22,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Net;
+using System.Text.Json;
namespace FoundationaLLM.Orchestration.Core.Orchestration
{
@@ -72,14 +75,16 @@ public class OrchestrationBuilder
instanceId,
result.Agent,
originalRequest.SessionId!,
- result.ExplodedObjects!,
+ result.ExplodedObjectsManager,
resourceProviderServices,
callContext.CurrentUserIdentity!,
logger);
if (result.Agent.AgentType == typeof(KnowledgeManagementAgent))
{
- var orchestrator = string.IsNullOrWhiteSpace(result.Agent.OrchestrationSettings?.Orchestrator)
+ var orchestrator = string.IsNullOrWhiteSpace(
+ result.Agent.Workflow?.WorkflowHost
+ ?? result.Agent.OrchestrationSettings?.Orchestrator) // TODO: Remove this fallback path after all agents are upgraded.
? LLMOrchestrationServiceNames.LangChain
: result.Agent.OrchestrationSettings?.Orchestrator;
@@ -102,7 +107,7 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(),
@@ -167,7 +172,7 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync? ExplodedObjects, bool DataSourceAccessDenied)> LoadAgent(
+ private static async Task<(AgentBase? Agent, AIModelBase? AIModel, APIEndpointConfiguration? APIEndpointConfiguration, ExplodedObjectsManager ExplodedObjectsManager, bool DataSourceAccessDenied)> LoadAgent(
string instanceId,
string agentName,
string? sessionId,
@@ -192,7 +197,7 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync();
+ var explodedObjectsManager = new ExplodedObjectsManager();
var agentBase = await agentResourceProvider.GetResourceAsync(
$"/{AgentResourceTypeNames.Agents}/{agentName}",
@@ -204,64 +209,78 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(
- prompt.Value,
- currentUserIdentity);
- explodedObjects.Add(retrievedPrompt.ObjectId!, retrievedPrompt);
- }
-
- foreach(var agentAIModel in agentWorkflow.AgentWorkflowAIModels)
- {
- var retrievedAIModel = await aiModelResourceProvider.GetResourceAsync(
- agentAIModel.Value.AIModelObjectId,
- currentUserIdentity);
- var retrievedAPIEndpointConfiguration = await configurationResourceProvider.GetResourceAsync(
- retrievedAIModel.EndpointObjectId!,
- currentUserIdentity);
-
- // Check if the AI model is the main model, if so check for overrides.
- if (agentAIModel.Key == "main_model")
+ var resourcePath = ResourcePath.GetResourcePath(resourceObjectId.ObjectId);
+ switch (resourcePath.MainResourceTypeName)
{
- mainAIModel = retrievedAIModel;
- mainAIModelAPIEndpointConfiguration = retrievedAPIEndpointConfiguration;
- // Agent Workflow AI Model overrides.
- if (agentAIModel.Value.ModelParameters != null)
- {
- // Allowing the override only for the keys that are supported.
- foreach (var key in agentAIModel.Value.ModelParameters.Keys.Where(k => ModelParametersKeys.All.Contains(k)))
+ case AIModelResourceTypeNames.AIModels:
+ // Check if the AI model is the main model, if so check for overrides.
+ if (resourceObjectId.Properties.TryGetValue(ResourceObjectIdPropertyNames.ObjectRole, out var aiModelObjectRole)
+ && ((JsonElement)aiModelObjectRole).GetString() == ResourceObjectIdPropertyValues.MainModel)
{
- retrievedAIModel.ModelParameters[key] = agentAIModel.Value.ModelParameters[key];
- }
- }
+ var retrievedAIModel = await aiModelResourceProvider.GetResourceAsync(
+ resourceObjectId.ObjectId,
+ currentUserIdentity);
+ var retrievedAPIEndpointConfiguration = await configurationResourceProvider.GetResourceAsync(
+ retrievedAIModel.EndpointObjectId!,
+ currentUserIdentity);
+
+ mainAIModel = retrievedAIModel;
+ mainAIModelAPIEndpointConfiguration = retrievedAPIEndpointConfiguration;
+
+ // Agent Workflow AI Model overrides.
+ if (resourceObjectId.Properties.TryGetValue(ResourceObjectIdPropertyNames.ModelParameters, out var modelParameters)
+ && modelParameters != null)
+ {
+ // Allowing the override only for the keys that are supported.
+ var modelParamsDict = JsonSerializer.Deserialize>(((JsonElement)modelParameters).GetRawText());
+ foreach (var key in modelParamsDict!.Keys.Where(k => ModelParametersKeys.All.Contains(k)))
+ {
+ retrievedAIModel.ModelParameters[key] = modelParamsDict[key];
+ }
+ }
+
+ explodedObjectsManager.TryAdd(
+ retrievedAIModel.ObjectId!,
+ retrievedAIModel);
+ explodedObjectsManager.TryAdd(
+ retrievedAIModel.EndpointObjectId!,
+ retrievedAPIEndpointConfiguration);
+ }
- // Request overrides for the main model.
- if (modelParameterOverrides != null)
- {
- // Allowing the override only for the keys that are supported.
- foreach (var key in modelParameterOverrides.Keys.Where(k => ModelParametersKeys.All.Contains(k)))
+ break;
+ case PromptResourceTypeNames.Prompts:
+ if (resourceObjectId.Properties.TryGetValue(ResourceObjectIdPropertyNames.ObjectRole, out var promptObjectRole)
+ && ((JsonElement)promptObjectRole).GetString() == ResourceObjectIdPropertyValues.MainPrompt)
{
- retrievedAIModel.ModelParameters[key] = modelParameterOverrides[key];
+ var retrievedPrompt = await promptResourceProvider.GetResourceAsync(
+ resourceObjectId.ObjectId,
+ currentUserIdentity);
+ explodedObjectsManager.TryAdd(
+ retrievedPrompt.ObjectId!,
+ retrievedPrompt);
}
- }
+ break;
+ case AgentResourceTypeNames.Workflows:
+ agentWorkflow.WorkflowName = resourcePath.MainResourceId;
+ break;
}
-
- explodedObjects.Add(retrievedAIModel.ObjectId!, retrievedAIModel);
- explodedObjects.Add(retrievedAIModel.EndpointObjectId!, retrievedAPIEndpointConfiguration);
}
- if (agentWorkflow is AzureOpenAIAssistantsAgentWorkflow)
+ if (agentWorkflow is AzureOpenAIAssistantsAgentWorkflow azureOpenAIAssistantsWorkflow)
{
- explodedObjects[CompletionRequestObjectsKeys.OpenAIAssistantsAssistantId] =
- ((AzureOpenAIAssistantsAgentWorkflow)agentWorkflow).AssistantId
- ?? throw new OrchestrationException("The OpenAI Assistants assistant identifier was not found in the agent workflow.");
+ explodedObjectsManager.TryAdd(
+ CompletionRequestObjectsKeys.OpenAIAssistantsAssistantId,
+ azureOpenAIAssistantsWorkflow.AssistantId
+ ?? throw new OrchestrationException("The OpenAI Assistants assistant identifier was not found in the agent workflow."));
}
}
else
{
- /**** LEGACY CODE ****/
+ #region Legacy agent loading code
+
var prompt = await promptResourceProvider.GetResourceAsync(
agentBase.PromptObjectId!,
currentUserIdentity);
@@ -274,9 +293,9 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(
@@ -303,19 +323,22 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(instanceId, currentUserIdentity);
- var allAgentsDescriptions = allAgents
- .Where(a => !string.IsNullOrWhiteSpace(a.Resource.Description) && a.Resource.Name != agentBase.Name)
- .Select(a => new
- {
- a.Resource.Name,
- a.Resource.Description
- })
- .ToDictionary(x => x.Name, x => x.Description);
- explodedObjects[CompletionRequestObjectsKeys.AllAgents] = allAgentsDescriptions;
+ // TODO: New agent-to-agent conversations model is in development. Until then, no need to send the list of all agents and their descriptions..
+
+ //var allAgents = await agentResourceProvider.GetResourcesAsync(instanceId, currentUserIdentity);
+ //var allAgentsDescriptions = allAgents
+ // .Where(a => !string.IsNullOrWhiteSpace(a.Resource.Description) && a.Resource.Name != agentBase.Name)
+ // .Select(a => new
+ // {
+ // a.Resource.Name,
+ // a.Resource.Description
+ // })
+ // .ToDictionary(x => x.Name, x => x.Description);
+ //explodedObjects[CompletionRequestObjectsKeys.AllAgents] = allAgentsDescriptions;
#region Tools
@@ -324,56 +347,94 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(
- aiModelObjectId,
- currentUserIdentity);
+ var resourcePath = ResourcePath.GetResourcePath(resourceObjectId.ObjectId);
- explodedObjects[aiModelObjectId] = toolAIModel;
+ // No need to explode objects that have already been exploded.
+ if (explodedObjectsManager.HasKey(resourceObjectId.ObjectId))
+ continue;
- var toolAPIEndpointConfiguration = await configurationResourceProvider.GetResourceAsync(
- toolAIModel.EndpointObjectId!,
- currentUserIdentity);
+ switch (resourcePath.MainResourceTypeName)
+ {
+ case AIModelResourceTypeNames.AIModels:
- explodedObjects[toolAIModel.EndpointObjectId!] = toolAPIEndpointConfiguration;
- }
+ var aiModel = await aiModelResourceProvider.GetResourceAsync(
+ resourceObjectId.ObjectId,
+ currentUserIdentity);
- foreach (var apiEndpointConfigurationObjectId in tool.APIEndpointConfigurationObjectIds.Values)
- {
- var toolAPIEndpointConfiguration = await configurationResourceProvider.GetResourceAsync(
- apiEndpointConfigurationObjectId,
- currentUserIdentity);
+ explodedObjectsManager.TryAdd(
+ resourceObjectId.ObjectId,
+ aiModel);
- explodedObjects[apiEndpointConfigurationObjectId] = toolAPIEndpointConfiguration;
- }
+ // TODO: Improve handling to allow each tool to override model parameters separately
+ if (!string.IsNullOrEmpty(aiModel.EndpointObjectId) && !explodedObjectsManager.HasKey(aiModel.EndpointObjectId))
+ {
+ var aiModelEndpoint = await configurationResourceProvider.GetResourceAsync(
+ aiModel.EndpointObjectId!,
+ currentUserIdentity);
- foreach (var indexingProfileObjectId in tool.IndexingProfileObjectIds.Values)
- {
- var indexingProfile = await vectorizationResourceProvider.GetResourceAsync(
- indexingProfileObjectId,
- currentUserIdentity);
+ explodedObjectsManager.TryAdd(
+ aiModel.EndpointObjectId!,
+ aiModelEndpoint);
+ }
- explodedObjects[indexingProfileObjectId] = indexingProfile;
+ break;
- // Provide the indexing profile API endpoint configuration.
- if (indexingProfile.Settings == null)
- throw new OrchestrationException($"Tool: {tool.Name}: The settings for the indexing profile {indexingProfile.Name} were not found. Must include \"{VectorizationSettingsNames.IndexingProfileApiEndpointConfigurationObjectId}\" setting.");
+ case ConfigurationResourceTypeNames.APIEndpointConfigurations:
+ var apiEndpoint = await configurationResourceProvider.GetResourceAsync(
+ resourceObjectId.ObjectId,
+ currentUserIdentity);
+
+ explodedObjectsManager.TryAdd(
+ resourceObjectId.ObjectId,
+ apiEndpoint);
+
+ break;
+
+ case VectorizationResourceTypeNames.IndexingProfiles:
+ var indexingProfile = await vectorizationResourceProvider.GetResourceAsync(
+ resourceObjectId.ObjectId,
+ currentUserIdentity);
+
+ explodedObjectsManager.TryAdd(
+ resourceObjectId.ObjectId,
+ indexingProfile);
- if (indexingProfile.Settings.TryGetValue(VectorizationSettingsNames.IndexingProfileApiEndpointConfigurationObjectId, out var apiEndpointConfigurationObjectId) == false)
- throw new OrchestrationException($"Tool: {tool.Name}: The API endpoint configuration object ID was not found in the settings of the indexing profile.");
+ if (indexingProfile.Settings == null)
+ throw new OrchestrationException($"Tool: {tool.Name}: Settings for indexing profile {indexingProfile.Name} not found.");
- var indexingProfileAPIEndpointConfiguration = await configurationResourceProvider.GetResourceAsync(
- apiEndpointConfigurationObjectId,
- currentUserIdentity);
+ if (!indexingProfile.Settings.TryGetValue(VectorizationSettingsNames.IndexingProfileApiEndpointConfigurationObjectId, out var apiEndpointConfigurationObjectId))
+ throw new OrchestrationException($"Tool: {tool.Name}: API endpoint configuration ID not found in indexing profile settings.");
+
+ if (!explodedObjectsManager.HasKey(apiEndpointConfigurationObjectId))
+ {
+ // Explode the object only if it hasn't been exploded yet.
+
+ var indexingProfileApiEndpoint = await configurationResourceProvider.GetResourceAsync(
+ apiEndpointConfigurationObjectId!,
+ currentUserIdentity);
+
+ explodedObjectsManager.TryAdd(
+ apiEndpointConfigurationObjectId,
+ indexingProfileApiEndpoint);
+ }
- explodedObjects[apiEndpointConfigurationObjectId] = indexingProfileAPIEndpointConfiguration;
+ break;
+
+ default:
+ throw new OrchestrationException($"Unknown resource type '{resourcePath.MainResourceTypeName}'.");
+ }
}
}
- explodedObjects[CompletionRequestObjectsKeys.ToolNames] = toolNames;
+ explodedObjectsManager.TryAdd(
+ CompletionRequestObjectsKeys.ToolNames,
+ toolNames);
#endregion
@@ -418,7 +479,9 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(
+ if (!explodedObjectsManager.HasKey(apiEndpointConfigurationObjectId))
+ {
+ // Explode the object only if it hasn't been exploded yet.
+
+ var indexingProfileAPIEndpointConfiguration = await configurationResourceProvider.GetResourceAsync(
apiEndpointConfigurationObjectId,
currentUserIdentity);
- explodedObjects[apiEndpointConfigurationObjectId] = indexingProfileAPIEndpointConfiguration;
+ explodedObjectsManager.TryAdd(
+ apiEndpointConfigurationObjectId,
+ indexingProfileAPIEndpointConfiguration);
+ }
}
- if (!string.IsNullOrWhiteSpace(kmAgent.Vectorization.TextEmbeddingProfileObjectId))
+ if (!string.IsNullOrWhiteSpace(kmAgent.Vectorization.TextEmbeddingProfileObjectId)
+ && !explodedObjectsManager.HasKey(kmAgent.Vectorization.TextEmbeddingProfileObjectId))
{
var textEmbeddingProfile = await vectorizationResourceProvider.GetResourceAsync(
kmAgent.Vectorization.TextEmbeddingProfileObjectId,
- currentUserIdentity);
-
- if (textEmbeddingProfile == null)
- throw new OrchestrationException($"The text embedding profile {kmAgent.Vectorization.TextEmbeddingProfileObjectId} is not a valid text embedding profile.");
+ currentUserIdentity)
+ ?? throw new OrchestrationException($"The text embedding profile {kmAgent.Vectorization.TextEmbeddingProfileObjectId} is not a valid text embedding profile.");
- explodedObjects[kmAgent.Vectorization.TextEmbeddingProfileObjectId!] = textEmbeddingProfile;
+ explodedObjectsManager.TryAdd(
+ kmAgent.Vectorization.TextEmbeddingProfileObjectId!,
+ textEmbeddingProfile);
}
}
}
#endregion
- return (agentBase, mainAIModel, mainAIModelAPIEndpointConfiguration, explodedObjects, false);
+ return (agentBase, mainAIModel, mainAIModelAPIEndpointConfiguration, explodedObjectsManager, false);
}
private static async Task EnsureAgentCapabilities(
string instanceId,
AgentBase agent,
string conversationId,
- Dictionary explodedObjects,
+ ExplodedObjectsManager explodedObjectsManager,
Dictionary resourceProviderServices,
UnifiedUserIdentity currentUserIdentity,
ILogger logger)
@@ -469,10 +540,10 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(mainAIModelObjectId!, out AIModelBase? aiModel);
+ explodedObjectsManager.TryGet(aiModel!.EndpointObjectId!, out APIEndpointConfiguration? apiEndpointConfiguration);
+ explodedObjectsManager.TryGet(CompletionRequestObjectsKeys.OpenAIAssistantsAssistantId, out string? openAIAssistantsAssistantId);
var resourceProviderUpsertOptions = new ResourceProviderUpsertOptions
{
@@ -528,14 +599,20 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync(agent.PromptObjectId!, out MultipartPrompt? prompt);
+ explodedObjectsManager.TryGet(agent.AIModelObjectId!, out AIModelBase? aiModel);
+ explodedObjectsManager.TryGet(aiModel!.EndpointObjectId!, out APIEndpointConfiguration? apiEndpointConfiguration);
+ explodedObjectsManager.TryGet(CompletionRequestObjectsKeys.OpenAIAssistantsAssistantId, out string? openAIAssistantsAssistantId);
var resourceProviderUpsertOptions = new ResourceProviderUpsertOptions
{
@@ -608,22 +687,28 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync C
messages.append(HumanMessage(content=request.user_prompt))
response = await graph.ainvoke({'messages': messages}, config={"configurable": {"original_user_prompt": request.user_prompt}})
# TODO: process tool messages with analysis results AIMessage with content='' but has addition_kwargs={'tool_calls';[...]}
-
+
# Get ContentArtifact items from ToolMessages
content_artifacts = []
tool_messages = [message for message in response["messages"] if isinstance(message, ToolMessage)]
@@ -446,7 +464,7 @@ async def invoke_async(self, request: KnowledgeManagementCompletionRequest) -> C
if isinstance(tool_message.artifact, list):
for item in tool_message.artifact:
if isinstance(item, ContentArtifact):
- content_artifacts.append(item)
+ content_artifacts.append(item)
final_message = response["messages"][-1]
response_content = OpenAITextMessageContentItem(
diff --git a/src/python/PythonSDK/foundationallm/langchain/tools/dalle_image_generation_tool.py b/src/python/PythonSDK/foundationallm/langchain/tools/dalle_image_generation_tool.py
index 695a9125f9..7bc4db4748 100644
--- a/src/python/PythonSDK/foundationallm/langchain/tools/dalle_image_generation_tool.py
+++ b/src/python/PythonSDK/foundationallm/langchain/tools/dalle_image_generation_tool.py
@@ -13,6 +13,14 @@
from foundationallm.models.resource_providers.ai_models import AIModelBase
from foundationallm.models.resource_providers.configuration import APIEndpointConfiguration
from foundationallm.utils import ObjectUtils
+from foundationallm.langchain.exceptions import LangChainException
+from foundationallm.models.constants import (
+ ResourceObjectIdPropertyNames,
+ ResourceObjectIdPropertyValues,
+ ResourceProviderNames,
+ AIModelResourceTypeNames,
+ PromptResourceTypeNames
+)
class DALLEImageGenerationToolQualityEnum(str, Enum):
""" Enum for the quality parameter of the DALL-E image generation tool. """
@@ -49,7 +57,17 @@ def __init__(self, tool_config: AgentTool, objects: dict, user_identity:UserIden
""" Initializes the DALLEImageGenerationTool class with the tool configuration,
exploded objects collection, user identity, and platform configuration. """
super().__init__(tool_config, objects, user_identity, config)
- self.ai_model = ObjectUtils.get_object_by_id(self.tool_config.ai_model_object_ids["main_model"], self.objects, AIModelBase)
+
+ ai_model_object_id = self.tool_config.get_resource_object_id_properties(
+ ResourceProviderNames.FOUNDATIONALLM_AIMODEL,
+ AIModelResourceTypeNames.AI_MODELS,
+ ResourceObjectIdPropertyNames.OBJECT_ROLE,
+ ResourceObjectIdPropertyValues.MAIN_MODEL
+ )
+ if ai_model_object_id is None:
+ raise LangChainException("The tools's AI models requires a main_model.", 400)
+
+ self.ai_model = ObjectUtils.get_object_by_id(ai_model_object_id.object_id, self.objects, AIModelBase)
self.api_endpoint = ObjectUtils.get_object_by_id(self.ai_model.endpoint_object_id, self.objects, APIEndpointConfiguration)
self.client = self._get_client()
diff --git a/src/python/PythonSDK/foundationallm/models/agents/__init__.py b/src/python/PythonSDK/foundationallm/models/agents/__init__.py
index 9c2225f007..1e6755bf4a 100644
--- a/src/python/PythonSDK/foundationallm/models/agents/__init__.py
+++ b/src/python/PythonSDK/foundationallm/models/agents/__init__.py
@@ -12,3 +12,4 @@
from .knowledge_management_agent import KnowledgeManagementAgent
from .knowledge_management_completion_request import KnowledgeManagementCompletionRequest
from .knowledge_management_index_configuration import KnowledgeManagementIndexConfiguration
+from .resource_object_ids_model_base import ResourceObjectIdsModelBase
diff --git a/src/python/PythonSDK/foundationallm/models/agents/agent_tool.py b/src/python/PythonSDK/foundationallm/models/agents/agent_tool.py
index 646056ab5e..ac4c070470 100644
--- a/src/python/PythonSDK/foundationallm/models/agents/agent_tool.py
+++ b/src/python/PythonSDK/foundationallm/models/agents/agent_tool.py
@@ -1,18 +1,15 @@
"""
Encapsulates properties an agent tool
"""
-from typing import Optional
-from pydantic import BaseModel, Field
+from typing import Optional, Dict
+from pydantic import Field
+from .resource_object_ids_model_base import ResourceObjectIdsModelBase
-class AgentTool(BaseModel):
+class AgentTool(ResourceObjectIdsModelBase):
"""
Encapsulates properties for an agent tool.
"""
name: str = Field(..., description="The name of the agent tool.")
description: str = Field(..., description="The description of the agent tool.")
package_name: str = Field(..., description="The package name of the agent tool. For internal tools, this value will be FoundationaLLM. For external tools, this value will be the name of the package.")
- ai_model_object_ids: Optional[dict] = Field(default=[], description="A dictionary object identifiers of the AIModel objects for the agent tool.")
- api_endpoint_configuration_object_ids: Optional[dict] = Field(default=[], description="A dictionary object identifiers of the APIEndpointConfiguration objects for the agent tool.")
- indexing_profile_object_ids: Optional[dict] = Field(default=[], description="A dictionary object identifiers of the IndexingProfile objects for the agent tool.")
- text_embedding_model_names: Optional[dict] = Field(default=[], description="A dictionary of text embedding model names for the agent tool.")
properties: Optional[dict] = Field(default=[], description="A dictionary of properties for the agent tool.")
diff --git a/src/python/PythonSDK/foundationallm/models/agents/agent_workflows/agent_workflow_base.py b/src/python/PythonSDK/foundationallm/models/agents/agent_workflows/agent_workflow_base.py
index 1199080fbd..7e6e80da7d 100644
--- a/src/python/PythonSDK/foundationallm/models/agents/agent_workflows/agent_workflow_base.py
+++ b/src/python/PythonSDK/foundationallm/models/agents/agent_workflows/agent_workflow_base.py
@@ -1,18 +1,16 @@
from pydantic import BaseModel, Field
from typing import Any, Self, Optional, Dict
-from .agent_workflow_ai_model import AgentWorkflowAIModel
+from ..resource_object_ids_model_base import ResourceObjectIdsModelBase
from foundationallm.utils import ObjectUtils
from foundationallm.langchain.exceptions import LangChainException
-class AgentWorkflowBase(BaseModel):
+class AgentWorkflowBase(ResourceObjectIdsModelBase):
"""
The base class used for an agent workflow.
"""
type: Optional[str] = Field(None, alias="type")
- workflow_object_id: str = Field(..., alias="workflow_object_id")
- workflow_name: str = Field(..., alias="workflow_name")
- agent_workflow_ai_models: Dict[str, AgentWorkflowAIModel] = Field(default_factory=dict, alias="agent_workflow_ai_models")
- prompt_object_ids: Dict[str, str] = Field(default_factory=dict, alias="prompt_object_ids")
+ workflow_host: str = Field(None, alias="workflow_host")
+ workflow_name: str = Field(None, alias="workflow_name")
@staticmethod
def from_object(obj: Any) -> Self:
@@ -21,7 +19,7 @@ def from_object(obj: Any) -> Self:
agent_workflow_base = AgentWorkflowBase(**ObjectUtils.translate_keys(obj))
except Exception as e:
raise LangChainException(f"The Agent Workflow base model object provided is invalid. {str(e)}", 400)
-
+
if agent_workflow_base is None:
raise LangChainException("The Agent Workflow base model object provided is invalid.", 400)
diff --git a/src/python/PythonSDK/foundationallm/models/agents/resource_object_id_properties.py b/src/python/PythonSDK/foundationallm/models/agents/resource_object_id_properties.py
new file mode 100644
index 0000000000..75f1b4d24b
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/agents/resource_object_id_properties.py
@@ -0,0 +1,18 @@
+from pydantic import BaseModel, Field, computed_field
+from typing import Optional, Any, Self, ClassVar
+from foundationallm.utils import ObjectUtils
+from foundationallm.langchain.exceptions import LangChainException
+from foundationallm.models.resource_providers import ResourcePath
+
+class ResourceObjectIdProperties(BaseModel):
+ """
+ Provides properties associated with a FoundationaLLM resource object identifier.
+ """
+ object_id: str = Field(description="The FoundationaLLM resource object identifier.")
+ resource_path: Optional[ResourcePath] = Field(None, description="The resource path object.")
+ properties: Optional[dict] = Field(default={}, description="A dictionary containing properties associated with the object identifier.")
+
+ def __init__(self, /, **data: Any) -> None:
+ super().__init__(**data)
+ self.resource_path = ResourcePath.parse(self.object_id)
+
diff --git a/src/python/PythonSDK/foundationallm/models/agents/resource_object_ids_model_base.py b/src/python/PythonSDK/foundationallm/models/agents/resource_object_ids_model_base.py
new file mode 100644
index 0000000000..064f1facd9
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/agents/resource_object_ids_model_base.py
@@ -0,0 +1,35 @@
+from pydantic import BaseModel, Field
+from typing import Dict
+from .resource_object_id_properties import ResourceObjectIdProperties
+
+class ResourceObjectIdsModelBase(BaseModel):
+ """
+ The base model class for all models that contain a dictionary of resource object identifier properties.
+ """
+
+ resource_object_ids: Dict[str, ResourceObjectIdProperties] = Field(default_factory=dict, alias="resource_object_ids")
+
+ def get_resource_object_id_properties(
+ self,
+ resource_provider_name: str,
+ resource_type_name: str,
+ property_name: str,
+ property_value: str) -> ResourceObjectIdProperties:
+
+ """
+ Gets the resource object identifier properties for a specific resource provider, resource type, and object role.
+
+ Args:
+ resource_provider_name (str): The resource provider name.
+ resource_type_name (str): The resource type name.
+ object_role (str): The object role.
+
+ Returns:
+ ResourceObjectIdProperties: The resource object identifier properties.
+ """
+ return next(
+ (v for v in self.resource_object_ids.values() \
+ if v.resource_path.resource_provider == resource_provider_name \
+ and v.resource_path.main_resource_type == resource_type_name \
+ and property_name in v.properties \
+ and v.properties[property_name] == property_value), None)
\ No newline at end of file
diff --git a/src/python/PythonSDK/foundationallm/models/constants/__init__.py b/src/python/PythonSDK/foundationallm/models/constants/__init__.py
index b74b338922..50e7476ffe 100644
--- a/src/python/PythonSDK/foundationallm/models/constants/__init__.py
+++ b/src/python/PythonSDK/foundationallm/models/constants/__init__.py
@@ -1 +1,7 @@
from .agent_capability_categories import AgentCapabilityCategories
+from .resource_object_id_property_names import ResourceObjectIdPropertyNames
+from .resource_object_id_property_values import ResourceObjectIdPropertyValues
+from .resource_provider_names import ResourceProviderNames
+
+from .ai_model_resource_type_names import AIModelResourceTypeNames
+from .prompt_resource_type_names import PromptResourceTypeNames
\ No newline at end of file
diff --git a/src/python/PythonSDK/foundationallm/models/constants/ai_model_resource_type_names.py b/src/python/PythonSDK/foundationallm/models/constants/ai_model_resource_type_names.py
new file mode 100644
index 0000000000..4f34180475
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/constants/ai_model_resource_type_names.py
@@ -0,0 +1,5 @@
+from enum import Enum
+
+class AIModelResourceTypeNames(str, Enum):
+ """The names of the resource types managed by the FoundationaLLM.AIModel resource provider."""
+ AI_MODELS = 'aiModels'
\ No newline at end of file
diff --git a/src/python/PythonSDK/foundationallm/models/constants/prompt_resource_type_names.py b/src/python/PythonSDK/foundationallm/models/constants/prompt_resource_type_names.py
new file mode 100644
index 0000000000..fb124acac2
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/constants/prompt_resource_type_names.py
@@ -0,0 +1,5 @@
+from enum import Enum
+
+class PromptResourceTypeNames(str, Enum):
+ """The names of the resource types managed by the FoundationaLLM.Prompt resource provider."""
+ PROMPTS = 'prompts'
\ No newline at end of file
diff --git a/src/python/PythonSDK/foundationallm/models/constants/resource_object_id_property_names.py b/src/python/PythonSDK/foundationallm/models/constants/resource_object_id_property_names.py
new file mode 100644
index 0000000000..2ede4d5ef4
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/constants/resource_object_id_property_names.py
@@ -0,0 +1,8 @@
+from enum import Enum
+
+class ResourceObjectIdPropertyNames(str, Enum):
+ """Allowed keys for resource object id properties dictionary entries."""
+ OBJECT_ROLE = 'object_role'
+ MODEL_PARAMETERS = 'model_parameters'
+ TEXT_EMBEDDING_MODEL_NAME = 'text_embedding_model_name'
+ TEXT_EMBEDDING_MODEL_PARAMETERS = 'text_embedding_model_parameters'
\ No newline at end of file
diff --git a/src/python/PythonSDK/foundationallm/models/constants/resource_object_id_property_values.py b/src/python/PythonSDK/foundationallm/models/constants/resource_object_id_property_values.py
new file mode 100644
index 0000000000..75cc01553f
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/constants/resource_object_id_property_values.py
@@ -0,0 +1,7 @@
+from enum import Enum
+
+class ResourceObjectIdPropertyValues(str, Enum):
+ """Allowed values for resource object id properties dictionary entries."""
+ MAIN_MODEL = 'main_model'
+ MAIN_PROMPT = 'main_prompt'
+ MAIN_INDEXING_PROFILE = 'main_indexing_profile'
\ No newline at end of file
diff --git a/src/python/PythonSDK/foundationallm/models/constants/resource_provider_names.py b/src/python/PythonSDK/foundationallm/models/constants/resource_provider_names.py
new file mode 100644
index 0000000000..63c387bf9d
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/constants/resource_provider_names.py
@@ -0,0 +1,6 @@
+from enum import Enum
+
+class ResourceProviderNames(str, Enum):
+ """The names of the FoundationaLLM resource providers."""
+ FOUNDATIONALLM_AIMODEL = 'FoundationaLLM.AIModel'
+ FOUNDATIONALLM_PROMPT = 'FoundationaLLM.Prompt'
\ No newline at end of file
diff --git a/src/python/PythonSDK/foundationallm/models/resource_providers/__init__.py b/src/python/PythonSDK/foundationallm/models/resource_providers/__init__.py
index 33fa2c2696..38bd9910b0 100644
--- a/src/python/PythonSDK/foundationallm/models/resource_providers/__init__.py
+++ b/src/python/PythonSDK/foundationallm/models/resource_providers/__init__.py
@@ -1,2 +1,3 @@
from .resource_name import ResourceName
from .resource_base import ResourceBase
+from .resource_path import ResourcePath
diff --git a/src/python/PythonSDK/foundationallm/models/resource_providers/resource_path.py b/src/python/PythonSDK/foundationallm/models/resource_providers/resource_path.py
new file mode 100644
index 0000000000..7200fac818
--- /dev/null
+++ b/src/python/PythonSDK/foundationallm/models/resource_providers/resource_path.py
@@ -0,0 +1,46 @@
+from pydantic import BaseModel, Field
+from typing import Self, Optional, ClassVar
+
+class ResourcePath(BaseModel):
+ """
+ Provides properties associated with a FoundationaLLM resource path.
+ """
+
+ INSTANCE_TOKEN: ClassVar[str] = 'instances'
+ RESOURCE_PROVIDER_TOKEN: ClassVar[str] = 'providers'
+
+ instance_id: Optional[str] = Field(description="The FoundationaLLM instance identifier.")
+ resource_provider: Optional[str] = Field(description="The FoundationaLLM resource provider.")
+ main_resource_type: Optional[str] = Field(description="The main resource type of the resource path.")
+ main_resource_id: Optional[str] = Field(None, description="The main resource identifier of the resource path.")
+
+ @staticmethod
+ def parse(object_id: str) -> Self:
+ """
+ Parses a resource path string into a ResourcePath object.
+
+ Args:
+ resource_path (str): The resource path string to parse.
+
+ Returns:
+ ResourcePath: The parsed ResourcePath object.
+ """
+ resource_path: ResourcePath = None
+
+ parts = object_id.strip("/").split("/")
+ if len(parts) < 5 \
+ or parts[0] != ResourcePath.INSTANCE_TOKEN \
+ or parts[1].strip() == "" \
+ or parts[2] != ResourcePath.RESOURCE_PROVIDER_TOKEN \
+ or parts[3].strip() == "" \
+ or parts[4].strip() == "" :
+ raise ValueError("The resource path is invalid.")
+ resource_path = ResourcePath(
+ instance_id=parts[1],
+ resource_provider=parts[3],
+ main_resource_type=parts[4])
+ if (len(parts) >= 6 \
+ and parts[5].strip() != ""):
+ resource_path.main_resource_id = parts[5]
+
+ return resource_path
diff --git a/src/python/PythonSDK/foundationallm/storage/blob_storage_manager.py b/src/python/PythonSDK/foundationallm/storage/blob_storage_manager.py
index 869249d1e6..0d7fa325c3 100644
--- a/src/python/PythonSDK/foundationallm/storage/blob_storage_manager.py
+++ b/src/python/PythonSDK/foundationallm/storage/blob_storage_manager.py
@@ -1,5 +1,6 @@
from io import BytesIO
import fnmatch
+import os
from azure.storage.blob import BlobServiceClient
from foundationallm.storage import StorageManagerBase
from azure.identity import DefaultAzureCredential
@@ -23,7 +24,20 @@ def __init__(self, blob_connection_string=None, container_name=None, account_nam
if authentication_type == 'AzureIdentity':
if account_name is None or account_name == '':
raise ValueError('The account_name parameter must be set to a valid account name.')
- credential = DefaultAzureCredential(exclude_environment_credential=True)
+
+ credential = \
+ DefaultAzureCredential(
+ exclude_workload_identity_credentials=True,
+ exclude_developer_cli_credential=False,
+ exclude_cli_credential=False,
+ exclude_environment_credential=True,
+ exclude_managed_identity_credential=True,
+ exclude_powershell_credential=True,
+ exclude_visual_studio_code_credential=True,
+ exclude_shared_token_cache_credentials=True,
+ exclude_interactive_browser_credential=True) if os.getenv('FOUNDATIONALLM_CONTEXT', 'NONE') == 'DEBUG' \
+ else DefaultAzureCredential(exclude_environment_credential=True)
+
blob_service_client = BlobServiceClient(account_url=f"https://{account_name}.blob.core.windows.net", credential=credential)
else:
if blob_connection_string is None or blob_connection_string == '':