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 == '':