diff --git a/deploy/standard-hub/infra/core/networking/vpnGateway.bicep b/deploy/standard-hub/infra/core/networking/vpnGateway.bicep index c0c1a53365..982fe0db7c 100644 --- a/deploy/standard-hub/infra/core/networking/vpnGateway.bicep +++ b/deploy/standard-hub/infra/core/networking/vpnGateway.bicep @@ -5,7 +5,7 @@ param location string param project string param subnetId string param tags object -param vpnClientAddressPool string = '192.168.101.0/28' +param vpnClientAddressPool string @allowed([ 'VpnGw1' diff --git a/deploy/standard-hub/infra/main.bicep b/deploy/standard-hub/infra/main.bicep index 6fc864f630..d9898eaecd 100644 --- a/deploy/standard-hub/infra/main.bicep +++ b/deploy/standard-hub/infra/main.bicep @@ -6,6 +6,7 @@ param environmentName string param location string param project string param timestamp string = utcNow() +param vpnClientAddressPool string // Locals var abbrs = loadJsonContent('./abbreviations.json') @@ -130,6 +131,7 @@ module vpn './core/networking/vpnGateway.bicep' = { project: project subnetId: '${vnet.outputs.vnetId}/subnets/GatewaySubnet' tags: tags + vpnClientAddressPool: vpnClientAddressPool } scope: rg } diff --git a/deploy/standard-hub/infra/main.parameters.json b/deploy/standard-hub/infra/main.parameters.json index f1600cfbcf..ff3e95fbcc 100644 --- a/deploy/standard-hub/infra/main.parameters.json +++ b/deploy/standard-hub/infra/main.parameters.json @@ -2,11 +2,17 @@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { + "cidrVnet": { + "value": "${FLLM_HUB_CIDR_VNET=192.168.100.0/24}" + }, "environmentName": { "value": "${AZURE_ENV_NAME}" }, "location": { "value": "${AZURE_LOCATION}" + }, + "vpnClientAddressPool": { + "value": "${VPN_CLIENT_ADDRESS_POOL=192.168.101.0/24}" } } } diff --git a/docs/release-notes/breaking-changes.md b/docs/release-notes/breaking-changes.md index 19e63f057d..5e3dfd716f 100644 --- a/docs/release-notes/breaking-changes.md +++ b/docs/release-notes/breaking-changes.md @@ -67,6 +67,12 @@ The `FoundationaLLM:APIs` and `FoundationaLLM:ExternalAPIs` configuration namesp The `FoundationaLLM:AzureAIStudio` configuration namespace expects an `APIEndpointConfigurationName` property instead of `BaseUrl`. +A new configuration setting named `FoundationaLLM:Instance:SecurityGroupRetrievalStrategy` with a value of `IdentityManagementService` must exist in the app configuration. It will be added by default in new deployments. + +Two new configuration settings required by the new `FoundationaLLM.AzureOpenAI` resource provider: +- `FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AuthenticationType` +- `FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AccountName` + ### Pre-0.8.0 1. Vectorization resource stores use a unique collection name, `Resources`. They also add a new top-level property named `DefaultResourceName`. diff --git a/src/FoundationaLLM.sln b/src/FoundationaLLM.sln index 8faaad2998..bbf7898592 100644 --- a/src/FoundationaLLM.sln +++ b/src/FoundationaLLM.sln @@ -117,9 +117,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AIModel", "dotnet\AIModel\A EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GatewayAdapterAPI", "dotnet\GatewayAdapterAPI\GatewayAdapterAPI.csproj", "{F826A354-9DF5-4DE5-97CB-F8F0D4566C0F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "State", "dotnet\State\State.csproj", "{BF7614CE-4E64-4E69-A56D-A7E72EDA8ABD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "State", "dotnet\State\State.csproj", "{BF7614CE-4E64-4E69-A56D-A7E72EDA8ABD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateAPI", "dotnet\StateAPI\StateAPI.csproj", "{B0D4EBB4-2057-4081-8A80-65390BFBA654}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateAPI", "dotnet\StateAPI\StateAPI.csproj", "{B0D4EBB4-2057-4081-8A80-65390BFBA654}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resource_providers", "resource_providers", "{2C948535-3001-4852-9686-492A23E9E356}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureOpenAI", "dotnet\AzureOpenAI\AzureOpenAI.csproj", "{71DD0475-532B-49C0-8699-6552FF3A4A6E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -310,6 +314,10 @@ Global {B0D4EBB4-2057-4081-8A80-65390BFBA654}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0D4EBB4-2057-4081-8A80-65390BFBA654}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0D4EBB4-2057-4081-8A80-65390BFBA654}.Release|Any CPU.Build.0 = Release|Any CPU + {71DD0475-532B-49C0-8699-6552FF3A4A6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71DD0475-532B-49C0-8699-6552FF3A4A6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71DD0475-532B-49C0-8699-6552FF3A4A6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71DD0475-532B-49C0-8699-6552FF3A4A6E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -337,7 +345,7 @@ Global {8FACDBA2-3FAD-4DBA-812A-67B9D1892F32} = {23275624-C0DA-4E93-9291-081D75E8CCD2} {A09449F4-008C-4BD8-AF3A-A98B7603D85D} = {23275624-C0DA-4E93-9291-081D75E8CCD2} {0DEF31F7-72E4-428A-A32B-C11F9C28B192} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} - {3A73EEED-1602-4CAC-BAAD-56062A3431CC} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} + {3A73EEED-1602-4CAC-BAAD-56062A3431CC} = {2C948535-3001-4852-9686-492A23E9E356} {3D8E64BB-C0D0-433A-AC4C-B9FFCFA4E013} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {7EE65CBF-9052-4C02-BCDD-61C88A1E10E1} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {F74CBB5A-97B9-4F68-8077-8BAB49C841DC} = {32FD7A00-B42E-4B0B-B09C-9CE48D8E047B} @@ -346,28 +354,30 @@ Global {46FB5F1B-57C6-4CA3-B626-887DF6D806DD} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {2D54392A-8D86-4F54-9993-FB3B6C4C090E} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {CDB843FE-108B-435A-BF17-68052C64F500} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} - {9BE97AEC-032C-454B-BDAA-29418A769237} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} - {E04B4AA1-2C79-4153-87A0-2BAC1256E4DE} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} + {9BE97AEC-032C-454B-BDAA-29418A769237} = {2C948535-3001-4852-9686-492A23E9E356} + {E04B4AA1-2C79-4153-87A0-2BAC1256E4DE} = {2C948535-3001-4852-9686-492A23E9E356} {D2257807-4A0A-4324-A10B-D160550C9E1E} = {28E0E967-A94D-4820-8A61-0B71D3B2780F} {7DD70AFD-69C4-4C82-9B67-5EE4A671D741} = {28E0E967-A94D-4820-8A61-0B71D3B2780F} {9273591A-0499-4DAB-ACDA-3565586B9881} = {28E0E967-A94D-4820-8A61-0B71D3B2780F} - {355E060E-2214-4001-8810-5E2381E1C075} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} - {9E243B01-0DA5-4E7B-9539-7E5BF87106EE} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} - {10D4F412-0F01-485F-957E-9EA8F16AED10} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} + {355E060E-2214-4001-8810-5E2381E1C075} = {2C948535-3001-4852-9686-492A23E9E356} + {9E243B01-0DA5-4E7B-9539-7E5BF87106EE} = {2C948535-3001-4852-9686-492A23E9E356} + {10D4F412-0F01-485F-957E-9EA8F16AED10} = {2C948535-3001-4852-9686-492A23E9E356} {63D7285C-0C40-49E9-BB4D-8AD2E0A29E62} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {35833700-B5EF-4211-91C7-E12A03DE3B82} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {2A51F9EB-BFCB-4285-A6FC-80CCA46228FB} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {6230C568-E97B-4959-8ECE-33D97D1BBD4C} = {28E0E967-A94D-4820-8A61-0B71D3B2780F} - {8F66055B-3A50-4F9B-A774-7D17F2D4A523} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} + {8F66055B-3A50-4F9B-A774-7D17F2D4A523} = {2C948535-3001-4852-9686-492A23E9E356} {31062ADB-88CD-401A-8DFE-B070DD91CFFD} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {C757E76C-AE42-4700-A92C-ED321D9CDB39} = {28E0E967-A94D-4820-8A61-0B71D3B2780F} {95676184-9498-465C-B4FE-C67AA09A0397} = {C757E76C-AE42-4700-A92C-ED321D9CDB39} {69E631C8-3266-4D40-9A7A-D67C07972BE2} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {2B369949-0297-485E-9455-E8F54D078DB3} = {C757E76C-AE42-4700-A92C-ED321D9CDB39} - {58D9C40B-3BE6-42CC-9FAB-ECF20FDA2E84} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} + {58D9C40B-3BE6-42CC-9FAB-ECF20FDA2E84} = {2C948535-3001-4852-9686-492A23E9E356} {F826A354-9DF5-4DE5-97CB-F8F0D4566C0F} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {BF7614CE-4E64-4E69-A56D-A7E72EDA8ABD} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} {B0D4EBB4-2057-4081-8A80-65390BFBA654} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} + {2C948535-3001-4852-9686-492A23E9E356} = {B6DC1190-2873-44A3-85B3-63D7BDE99231} + {71DD0475-532B-49C0-8699-6552FF3A4A6E} = {2C948535-3001-4852-9686-492A23E9E356} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FF5DE858-4B85-4EE8-8A6D-46E8E4FBA078} diff --git a/src/dotnet/Agent/ResourceProviders/AgentResourceProviderService.cs b/src/dotnet/Agent/ResourceProviders/AgentResourceProviderService.cs index b7a2b87a19..0153558540 100644 --- a/src/dotnet/Agent/ResourceProviders/AgentResourceProviderService.cs +++ b/src/dotnet/Agent/ResourceProviders/AgentResourceProviderService.cs @@ -2,6 +2,7 @@ using FluentValidation; using FoundationaLLM.Agent.Models.Resources; using FoundationaLLM.Common.Constants; +using FoundationaLLM.Common.Constants.Agents; using FoundationaLLM.Common.Constants.Authorization; using FoundationaLLM.Common.Constants.Configuration; using FoundationaLLM.Common.Constants.ResourceProviders; @@ -234,7 +235,6 @@ private async Task UpdateAgent(ResourcePath resour }; agent.ObjectId = resourcePath.GetObjectId(_instanceSettings.Id, _name); - agent.Capabilities ??= [AgentCapabilities.OpenAIAssistants]; if ((agent is KnowledgeManagementAgent {Vectorization.DedicatedPipeline: true, InlineContext: false} kmAgent)) { @@ -312,8 +312,8 @@ protected override async Task ExecuteActionAsync(ResourcePath resourcePa { AgentResourceTypeNames.Agents => resourcePath.ResourceTypeInstances.Last().Action switch { - AgentResourceProviderActions.CheckName => CheckAgentName(serializedAction), - AgentResourceProviderActions.Purge => await PurgeResource(resourcePath), + ResourceProviderActions.CheckName => CheckAgentName(serializedAction), + ResourceProviderActions.Purge => await PurgeResource(resourcePath), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, diff --git a/src/dotnet/Authorization/Data/AuthorizableActions.json b/src/dotnet/Authorization/Data/AuthorizableActions.json index e73b568bb4..d275c8c6cb 100644 --- a/src/dotnet/Authorization/Data/AuthorizableActions.json +++ b/src/dotnet/Authorization/Data/AuthorizableActions.json @@ -44,6 +44,26 @@ } ] }, + { + "category": "AzureOpenAI", + "actions": [ + { + "name": "FoundationaLLM.AzureOpenAI/assistantUserContexts/read", + "description": "Read assistant user contexts.", + "field_name": "FoundationaLLM_AzureOpenAI_AssistantUserContexts_Read" + }, + { + "name": "FoundationaLLM.AzureOpenAI/assistantUserContexts/write", + "description": "Create or update assistant user contexts.", + "field_name": "FoundationaLLM_AzureOpenAI_AssistantUserContexts_Write" + }, + { + "name": "FoundationaLLM.AzureOpenAI/assistantUserContexts/delete", + "description": "Delete assistant user contexts.", + "field_name": "FoundationaLLM_AzureOpenAI_AssistantUserContexts_Delete" + } + ] + }, { "category": "Configuration", "actions": [ @@ -78,19 +98,19 @@ "field_name": "FoundationaLLM_Configuration_KeyVaultSecrets_Delete" }, { - "name": "FoundationaLLM.Configuration/apiEndpoints/read", - "description": "Read API endpoints.", - "field_name": "FoundationaLLM_Configuration_APIEndpoints_Read" + "name": "FoundationaLLM.Configuration/apiEndpointConfigurations/read", + "description": "Read API endpoint configurations.", + "field_name": "FoundationaLLM_Configuration_APIEndpointConfigurations_Read" }, { - "name": "FoundationaLLM.Configuration/apiEndpoints/write", - "description": "Create or update API endpoints.", - "field_name": "FoundationaLLM_Configuration_APIEndpoints_Write" + "name": "FoundationaLLM.Configuration/apiEndpointConfigurations/write", + "description": "Create or update API endpoint configurations.", + "field_name": "FoundationaLLM_Configuration_APIEndpointConfigurations_Write" }, { - "name": "FoundationaLLM.Configuration/apiEndpoints/delete", - "description": "Delete API endpoints.", - "field_name": "FoundationaLLM_Configuration_APIEndpoints_Delete" + "name": "FoundationaLLM.Configuration/apiEndpointConfigurations/delete", + "description": "Delete API endpoint configurations.", + "field_name": "FoundationaLLM_Configuration_APIEndpoinConfigurations_Delete" } ] }, @@ -289,18 +309,18 @@ "actions": [ { "name": "FoundationaLLM.AIModel/aiModels/read", - "description": "Read aiModels models", - "field_name": "FoundationaLLM_AIModel_Models_Read" + "description": "Read AI models", + "field_name": "FoundationaLLM_AIModel_AIModels_Read" }, { "name": "FoundationaLLM.AIModel/aiModels/write", - "description": "Create or update models.", - "field_name": "FoundationaLLM_AIModel_Models_Write" + "description": "Create or update AI models.", + "field_name": "FoundationaLLM_AIModel_AIModels_Write" }, { "name": "FoundationaLLM.AIModel/aiModels/delete", - "description": "Delete models.", - "field_name": "FoundationaLLM_AIModel_Models_Delete" + "description": "Delete AI models.", + "field_name": "FoundationaLLM_AIModel_AIModels_Delete" } ] } diff --git a/src/dotnet/Authorization/Models/AuthorizableActions.cs b/src/dotnet/Authorization/Models/AuthorizableActions.cs index 5b1ef0ef43..b5b0a8e16e 100644 --- a/src/dotnet/Authorization/Models/AuthorizableActions.cs +++ b/src/dotnet/Authorization/Models/AuthorizableActions.cs @@ -61,6 +61,27 @@ public static class AuthorizableActions "Delete agents.", "Agent") }, + { + AuthorizableActionNames.FoundationaLLM_AzureOpenAI_AssistantUserContexts_Read, + new AuthorizableAction( + AuthorizableActionNames.FoundationaLLM_AzureOpenAI_AssistantUserContexts_Read, + "Read assistant user contexts.", + "AzureOpenAI") + }, + { + AuthorizableActionNames.FoundationaLLM_AzureOpenAI_AssistantUserContexts_Write, + new AuthorizableAction( + AuthorizableActionNames.FoundationaLLM_AzureOpenAI_AssistantUserContexts_Write, + "Create or update assistant user contexts.", + "AzureOpenAI") + }, + { + AuthorizableActionNames.FoundationaLLM_AzureOpenAI_AssistantUserContexts_Delete, + new AuthorizableAction( + AuthorizableActionNames.FoundationaLLM_AzureOpenAI_AssistantUserContexts_Delete, + "Delete assistant user contexts.", + "AzureOpenAI") + }, { AuthorizableActionNames.FoundationaLLM_Configuration_AppConfigurations_Read, new AuthorizableAction( @@ -104,24 +125,24 @@ public static class AuthorizableActions "Configuration") }, { - AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoints_Read, + AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpointConfigurations_Read, new AuthorizableAction( - AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoints_Read, - "Read API endpoints.", + AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpointConfigurations_Read, + "Read API endpoint configurations.", "Configuration") }, { - AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoints_Write, + AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpointConfigurations_Write, new AuthorizableAction( - AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoints_Write, - "Create or update API endpoints.", + AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpointConfigurations_Write, + "Create or update API endpoint configurations.", "Configuration") }, { - AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoints_Delete, + AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoinConfigurations_Delete, new AuthorizableAction( - AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoints_Delete, - "Delete API endpoints.", + AuthorizableActionNames.FoundationaLLM_Configuration_APIEndpoinConfigurations_Delete, + "Delete API endpoint configurations.", "Configuration") }, { @@ -359,21 +380,21 @@ public static class AuthorizableActions AuthorizableActionNames.FoundationaLLM_AIModel_AIModels_Read, new AuthorizableAction( AuthorizableActionNames.FoundationaLLM_AIModel_AIModels_Read, - "Read AIModels.", + "Read AI models", "AIModel") }, { AuthorizableActionNames.FoundationaLLM_AIModel_AIModels_Write, new AuthorizableAction( AuthorizableActionNames.FoundationaLLM_AIModel_AIModels_Write, - "Create or update AIModels.", + "Create or update AI models.", "AIModel") }, { AuthorizableActionNames.FoundationaLLM_AIModel_AIModels_Delete, new AuthorizableAction( AuthorizableActionNames.FoundationaLLM_AIModel_AIModels_Delete, - "Delete AIModel.", + "Delete AI models.", "AIModel") }, }); diff --git a/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderMetadata.cs b/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderMetadata.cs index f40134f92e..183894e593 100644 --- a/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderMetadata.cs +++ b/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderMetadata.cs @@ -25,7 +25,7 @@ public class AuthorizationResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []) ], Actions = [ - new ResourceTypeAction(AuthorizationResourceProviderActions.Filter, false, true, [ + new ResourceTypeAction(ResourceProviderActions.Filter, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(RoleAssignmentQueryParameters)], [typeof(ResourceProviderGetResult)]) ]) ] diff --git a/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderService.cs b/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderService.cs index bf9a155636..5722dddbdb 100644 --- a/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderService.cs +++ b/src/dotnet/Authorization/ResourceProviders/AuthorizationResourceProviderService.cs @@ -162,7 +162,7 @@ protected override async Task ExecuteActionAsync(ResourcePath resourcePa { AuthorizationResourceTypeNames.RoleAssignments => resourcePath.ResourceTypeInstances.Last().Action switch { - AuthorizationResourceProviderActions.Filter => await FilterRoleAssignments(resourcePath.ResourceTypeInstances[0], serializedAction, userIdentity), + ResourceProviderActions.Filter => await FilterRoleAssignments(resourcePath.ResourceTypeInstances[0], serializedAction, userIdentity), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, diff --git a/src/dotnet/Authorization/Utils/ResourcePathUtils.cs b/src/dotnet/Authorization/Utils/ResourcePathUtils.cs index 4b5632e582..f91a66ce27 100644 --- a/src/dotnet/Authorization/Utils/ResourcePathUtils.cs +++ b/src/dotnet/Authorization/Utils/ResourcePathUtils.cs @@ -77,6 +77,7 @@ private static Dictionary GetAllowedResourceType ResourceProviderNames.FoundationaLLM_Attachment => AttachmentResourceProviderMetadata.AllowedResourceTypes, ResourceProviderNames.FoundationaLLM_Authorization => AuthorizationResourceProviderMetadata.AllowedResourceTypes, ResourceProviderNames.FoundationaLLM_AIModel => AIModelResourceProviderMetadata.AllowedResourceTypes, + ResourceProviderNames.FoundationaLLM_AzureOpenAI => AzureOpenAIResourceProviderMetadata.AllowedResourceTypes, _ => [] }; } diff --git a/src/dotnet/AzureOpenAI/AzureOpenAI.csproj b/src/dotnet/AzureOpenAI/AzureOpenAI.csproj new file mode 100644 index 0000000000..1b1bf295c0 --- /dev/null +++ b/src/dotnet/AzureOpenAI/AzureOpenAI.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + FoundationaLLM.AzureOpenAI + FoundationaLLM.AzureOpenAI + + + + + + + + diff --git a/src/dotnet/AzureOpenAI/Models/AzureOpenAIResourceReference.cs b/src/dotnet/AzureOpenAI/Models/AzureOpenAIResourceReference.cs new file mode 100644 index 0000000000..e2dd0ca786 --- /dev/null +++ b/src/dotnet/AzureOpenAI/Models/AzureOpenAIResourceReference.cs @@ -0,0 +1,25 @@ +using FoundationaLLM.Common.Constants.ResourceProviders; +using FoundationaLLM.Common.Exceptions; +using FoundationaLLM.Common.Models.ResourceProviders; +using FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI; +using System.Text.Json.Serialization; + +namespace FoundationaLLM.AzureOpenAI.Models +{ + /// + /// References an resource managed by the FoundationaLLM.AzureOpenAI resource provider. + /// + public class AzureOpenAIResourceReference : ResourceReference + { + /// + /// The object type of the resource. + /// + [JsonIgnore] + public Type ResourceType => + Type switch + { + AzureOpenAITypes.AssistantUserContext => typeof(AssistantUserContext), + _ => throw new ResourceProviderException($"The resource type {Type} is not supported.") + }; + } +} diff --git a/src/dotnet/AzureOpenAI/ResourceProviders/AzureOpenAIResourceProviderService.cs b/src/dotnet/AzureOpenAI/ResourceProviders/AzureOpenAIResourceProviderService.cs new file mode 100644 index 0000000000..f030e3dc19 --- /dev/null +++ b/src/dotnet/AzureOpenAI/ResourceProviders/AzureOpenAIResourceProviderService.cs @@ -0,0 +1,452 @@ +using FoundationaLLM.AzureOpenAI.Models; +using FoundationaLLM.Common.Constants; +using FoundationaLLM.Common.Constants.Agents; +using FoundationaLLM.Common.Constants.Configuration; +using FoundationaLLM.Common.Constants.OpenAI; +using FoundationaLLM.Common.Constants.ResourceProviders; +using FoundationaLLM.Common.Exceptions; +using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Models.Authentication; +using FoundationaLLM.Common.Models.Configuration.Instance; +using FoundationaLLM.Common.Models.ResourceProviders; +using FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI; +using FoundationaLLM.Common.Services.ResourceProviders; +using FoundationaLLM.Gateway.Client; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Collections.Concurrent; +using System.Text; +using System.Text.Json; + +namespace FoundationaLLM.AzureOpenAI.ResourceProviders +{ + /// + /// Implements the FoundationaLLM.AzureOpenAI resource provider. + /// + /// The options providing the with instance settings. + /// The providing authorization services. + /// The providing storage services. + /// The providing event services. + /// The providing the factory to create resource validators. + /// The of the main dependency injection container. + /// The used for logging. + public class AzureOpenAIResourceProviderService( + IOptions instanceOptions, + IAuthorizationService authorizationService, + [FromKeyedServices(DependencyInjectionKeys.FoundationaLLM_ResourceProviders_AzureOpenAI)] IStorageService storageService, + IEventService eventService, + IResourceValidatorFactory resourceValidatorFactory, + IServiceProvider serviceProvider, + ILogger logger) + : ResourceProviderServiceBase( + instanceOptions.Value, + authorizationService, + storageService, + eventService, + resourceValidatorFactory, + serviceProvider, + logger) + { + /// + protected override Dictionary GetResourceTypes() => + AzureOpenAIResourceProviderMetadata.AllowedResourceTypes; + + private ConcurrentDictionary _resourceReferences = []; + + private const string RESOURCE_REFERENCES_FILE_NAME = "_resource-references.json"; + private const string RESOURCE_REFERENCES_FILE_PATH = $"/{ResourceProviderNames.FoundationaLLM_AzureOpenAI}/{RESOURCE_REFERENCES_FILE_NAME}"; + + /// + protected override string _name => ResourceProviderNames.FoundationaLLM_AzureOpenAI; + + /// + protected override async Task InitializeInternal() + { + _logger.LogInformation("Starting to initialize the {ResourceProvider} resource provider...", _name); + + if (await _storageService.FileExistsAsync(_storageContainerName, RESOURCE_REFERENCES_FILE_PATH, default)) + { + var fileContent = await _storageService.ReadFileAsync(_storageContainerName, RESOURCE_REFERENCES_FILE_PATH, default); + var resourceReferenceStore = JsonSerializer.Deserialize>( + Encoding.UTF8.GetString(fileContent.ToArray())); + + _resourceReferences = new ConcurrentDictionary( + resourceReferenceStore!.ToDictionary()); + } + else + { + await _storageService.WriteFileAsync( + _storageContainerName, + RESOURCE_REFERENCES_FILE_PATH, + JsonSerializer.Serialize(new ResourceReferenceStore { ResourceReferences = [] }), + default, + default); + } + + _logger.LogInformation("The {ResourceProvider} resource provider was successfully initialized.", _name); + } + + #region Support for Management API + + /// + protected override async Task GetResourcesAsync(ResourcePath resourcePath, UnifiedUserIdentity userIdentity) => + resourcePath.ResourceTypeInstances[0].ResourceType switch + { + AzureOpenAIResourceTypeNames.AssistantUserContext => await LoadAssistantUserContexts(resourcePath.ResourceTypeInstances[0]), + _ => throw new ResourceProviderException($"The resource type {resourcePath.ResourceTypeInstances[0].ResourceType} is not supported by the {_name} resource provider.", + StatusCodes.Status400BadRequest) + }; + + #region Helpers for GetResourcesAsyncInternal + + private async Task>> LoadAssistantUserContexts(ResourceTypeInstance instance) + { + if (instance.ResourceId == null) + { + var userContexts = (await Task.WhenAll( + _resourceReferences.Values + .Where(r => !r.Deleted) + .Select(r => LoadAssistantUserContext(r)))) + .Where(r => r != null) + .ToList(); + + return userContexts.Select(r => new ResourceProviderGetResult() { Resource = r!, Actions = [], Roles = [] }).ToList(); + } + else + { + AssistantUserContext? userContext; + if (!_resourceReferences.TryGetValue(instance.ResourceId, out var userContextReference)) + { + userContext = await LoadAssistantUserContext(null, instance.ResourceId); + if (userContext != null) + { + return [new ResourceProviderGetResult() { Resource = userContext, Actions = [], Roles = [] }]; + } + return []; + } + + if (userContextReference.Deleted) + { + throw new ResourceProviderException( + $"Could not locate the {instance.ResourceId} resource.", + StatusCodes.Status404NotFound); + } + + userContext = await LoadAssistantUserContext(userContextReference); + if (userContext != null) + { + return [new ResourceProviderGetResult() { Resource = userContext, Actions = [], Roles = [] }]; + } + return []; + } + } + + private async Task LoadAssistantUserContext(AzureOpenAIResourceReference? resourceReference, string? resourceId = null) + { + if (resourceReference != null || !string.IsNullOrEmpty(resourceId)) + { + resourceReference ??= new AzureOpenAIResourceReference + { + Name = resourceId!, + Type = AzureOpenAITypes.AssistantUserContext, + Filename = $"/{_name}/{resourceId}.json", + Deleted = false + }; + if (await _storageService.FileExistsAsync(_storageContainerName, resourceReference.Filename, default)) + { + var fileContent = + await _storageService.ReadFileAsync(_storageContainerName, resourceReference.Filename, default); + var userContext = JsonSerializer.Deserialize( + Encoding.UTF8.GetString(fileContent.ToArray()), + resourceReference.ResourceType, + _serializerSettings) as AssistantUserContext + ?? throw new ResourceProviderException($"Failed to load the resource {resourceReference.Name}.", + StatusCodes.Status400BadRequest); + + if (!string.IsNullOrWhiteSpace(resourceId)) + { + resourceReference.Type = userContext.Type!; + _resourceReferences.AddOrUpdate(resourceReference.Name, resourceReference, (k, v) => resourceReference); + } + + return userContext; + } + + if (string.IsNullOrWhiteSpace(resourceId)) + { + // Remove the reference from the dictionary since the file does not exist. + _resourceReferences.TryRemove(resourceReference.Name, out _); + return null; + } + } + + throw new ResourceProviderException($"Could not locate the {resourceReference.Name} resource.", + StatusCodes.Status404NotFound); + } + + #endregion + + /// + protected override async Task UpsertResourceAsync(ResourcePath resourcePath, string serializedResource, UnifiedUserIdentity userIdentity) => + resourcePath.ResourceTypeInstances[0].ResourceType switch + { + AzureOpenAIResourceTypeNames.AssistantUserContext => await UpdateAssistantUserContext(resourcePath, serializedResource, userIdentity), + _ => throw new ResourceProviderException($"The resource type {resourcePath.ResourceTypeInstances[0].ResourceType} is not supported by the {_name} resource provider.", + StatusCodes.Status400BadRequest), + }; + + #region Helpers for UpsertResourceAsync + + private async Task UpdateAssistantUserContext(ResourcePath resourcePath, string serializedResource, UnifiedUserIdentity userIdentity) + { + var assistantUserContext = JsonSerializer.Deserialize(serializedResource) + ?? throw new ResourceProviderException("The object definition is invalid."); + + // Check if the resource was logically deleted. + if (_resourceReferences.TryGetValue(assistantUserContext.Name!, out var existingResourceReference) + && existingResourceReference!.Deleted) + throw new ResourceProviderException($"The resource {existingResourceReference.Name} cannot be added or updated.", + StatusCodes.Status400BadRequest); + + if (resourcePath.ResourceTypeInstances[0].ResourceId != assistantUserContext.Name) + throw new ResourceProviderException("The resource path does not match the object definition (name mismatch).", + StatusCodes.Status400BadRequest); + + var resourceReference = new AzureOpenAIResourceReference + { + Name = assistantUserContext.Name!, + Type = assistantUserContext.Type!, + Filename = $"/{_name}/{assistantUserContext.Name}.json", + Deleted = false + }; + + assistantUserContext.ObjectId = resourcePath.GetObjectId(_instanceSettings.Id, _name); + + var gatewayClient = new GatewayServiceClient( + await _serviceProvider.GetRequiredService() + .CreateClient(HttpClientNames.GatewayAPI, userIdentity), + _serviceProvider.GetRequiredService>()); + + var newOpenAIAssistantId = default(string); + var newOpenAIAssistantThreadId = default(string); + + if (existingResourceReference == null) + { + // Creating a new resource. + assistantUserContext.CreatedBy = userIdentity.UPN; + + var result = await gatewayClient!.CreateAgentCapability( + _instanceSettings.Id, + AgentCapabilityCategoryNames.OpenAIAssistants, + resourceReference.Name, + new() + { + { OpenAIAgentCapabilityParameterNames.CreateAssistant, true }, + { OpenAIAgentCapabilityParameterNames.CreateAssistantThread, true }, + { OpenAIAgentCapabilityParameterNames.Endpoint, assistantUserContext.Endpoint }, + { OpenAIAgentCapabilityParameterNames.ModelDeploymentName , assistantUserContext.ModelDeploymentName }, + { OpenAIAgentCapabilityParameterNames.AssistantPrompt, assistantUserContext.Prompt } + }); + + result.TryGetValue(OpenAIAgentCapabilityParameterNames.AssistantId, out var newOpenAIAssistantIdObject); + newOpenAIAssistantId = ((JsonElement)newOpenAIAssistantIdObject!).Deserialize(); + + result.TryGetValue(OpenAIAgentCapabilityParameterNames.AssistantThreadId, out var newOpenAIAssistantThreadIdObject); + newOpenAIAssistantThreadId = ((JsonElement)newOpenAIAssistantThreadIdObject!).Deserialize(); + + assistantUserContext.OpenAIAssistantId = newOpenAIAssistantId; + assistantUserContext.OpenAIAssistantCreatedOn = DateTimeOffset.UtcNow; + + var conversation = assistantUserContext.Conversations.Values + .SingleOrDefault(c => string.IsNullOrWhiteSpace(c.OpenAIThreadId)) + ?? throw new ResourceProviderException("Could not find a conversation with an empty assistant thread id."); + + conversation.OpenAIThreadId = newOpenAIAssistantThreadId; + conversation.OpenAIThreadCreatedOn = assistantUserContext.OpenAIAssistantCreatedOn; + } + else + { + assistantUserContext.UpdatedBy = userIdentity.UPN; + + var incompleteConversations = assistantUserContext.Conversations.Values + .Where(c => string.IsNullOrWhiteSpace(c.OpenAIThreadId)) + .ToList(); + + if (incompleteConversations.Count > 1) + throw new ResourceProviderException($"The Assistant user context {assistantUserContext.Name} contains more than one incomplete conversation. This indicates an inconsistent approach in the resource management flow."); + + if (incompleteConversations.Count == 1) + { + var result = await gatewayClient!.CreateAgentCapability( + _instanceSettings.Id, + AgentCapabilityCategoryNames.OpenAIAssistants, + resourceReference.Name, + new() + { + { OpenAIAgentCapabilityParameterNames.AssistantId, assistantUserContext.OpenAIAssistantId! }, + { OpenAIAgentCapabilityParameterNames.CreateAssistantThread, true }, + { OpenAIAgentCapabilityParameterNames.Endpoint, assistantUserContext.Endpoint } + }); + + result.TryGetValue(OpenAIAgentCapabilityParameterNames.AssistantThreadId, out var newOpenAIAssistantThreadIdObject); + newOpenAIAssistantThreadId = ((JsonElement)newOpenAIAssistantThreadIdObject!).Deserialize(); + + var conversation = assistantUserContext.Conversations.Values + .SingleOrDefault(c => string.IsNullOrWhiteSpace(c.OpenAIThreadId)) + ?? throw new ResourceProviderException("Could not find a conversation with an empty assistant thread id."); + + conversation.OpenAIThreadId = newOpenAIAssistantThreadId; + conversation.OpenAIThreadCreatedOn = DateTimeOffset.UtcNow; + } + } + + await _storageService.WriteFileAsync( + _storageContainerName, + resourceReference.Filename, + JsonSerializer.Serialize(assistantUserContext, _serializerSettings), + default, + default); + + _resourceReferences.AddOrUpdate(resourceReference.Name, resourceReference, (k, v) => v); + + await _storageService.WriteFileAsync( + _storageContainerName, + RESOURCE_REFERENCES_FILE_PATH, + JsonSerializer.Serialize(ResourceReferenceStore.FromDictionary(_resourceReferences.ToDictionary())), + default, + default); + + return new AssistantUserContextUpsertResult + { + ObjectId = (assistantUserContext as AssistantUserContext)!.ObjectId, + NewOpenAIAssistantId = newOpenAIAssistantId, + NewOpenAIAssistantThreadId = newOpenAIAssistantThreadId + }; + } + + #endregion + + /// +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + protected override async Task ExecuteActionAsync(ResourcePath resourcePath, string serializedAction, UnifiedUserIdentity userIdentity) => + resourcePath.ResourceTypeInstances.Last().ResourceType switch + { + AzureOpenAIResourceTypeNames.AssistantUserContext => resourcePath.ResourceTypeInstances.Last().Action switch + { + ResourceProviderActions.CheckName => CheckResourceName(serializedAction), + ResourceProviderActions.Purge => await PurgeResource(resourcePath), + _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", + StatusCodes.Status400BadRequest) + }, + _ => throw new ResourceProviderException() + }; +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + #region Helpers for ExecuteActionAsync + + private ResourceNameCheckResult CheckResourceName(string serializedAction) + { + var resourceName = JsonSerializer.Deserialize(serializedAction); + return _resourceReferences.Values.Any(r => r.Name == resourceName!.Name) + ? new ResourceNameCheckResult + { + Name = resourceName!.Name, + Type = resourceName.Type, + Status = NameCheckResultType.Denied, + Message = "A resource with the specified name already exists or was previously deleted and not purged." + } + : new ResourceNameCheckResult + { + Name = resourceName!.Name, + Type = resourceName.Type, + Status = NameCheckResultType.Allowed + }; + } + + private async Task PurgeResource(ResourcePath resourcePath) + { + throw new NotImplementedException("The Azure OpenAI resource cleanup is not implemented."); + +#pragma warning disable CS0162 // Unreachable code detected + var resourceName = resourcePath.ResourceTypeInstances.Last().ResourceId!; +#pragma warning restore CS0162 // Unreachable code detected + if (_resourceReferences.TryGetValue(resourceName, out var resourceReference)) + { + if (resourceReference.Deleted) + { + // Delete the resource file from storage. + await _storageService.DeleteFileAsync( + _storageContainerName, + resourceReference.Filename, + default); + + // Remove this resource reference from the store. + _resourceReferences.TryRemove(resourceName, out _); + + await _storageService.WriteFileAsync( + _storageContainerName, + RESOURCE_REFERENCES_FILE_PATH, + JsonSerializer.Serialize(ResourceReferenceStore.FromDictionary(_resourceReferences.ToDictionary())), + default, + default); + + return new ResourceProviderActionResult(true); + } + else + { + throw new ResourceProviderException( + $"The {resourceName} resource is not soft-deleted and cannot be purged.", + StatusCodes.Status400BadRequest); + } + } + else + { + throw new ResourceProviderException($"Could not locate the {resourceName} resource.", + StatusCodes.Status404NotFound); + } + } + + #endregion + + /// + protected override async Task DeleteResourceAsync(ResourcePath resourcePath, UnifiedUserIdentity userIdentity) + { + switch (resourcePath.ResourceTypeInstances.Last().ResourceType) + { + case AzureOpenAIResourceTypeNames.AssistantUserContext: + await DeleteAssistantUserContext(resourcePath.ResourceTypeInstances); + break; + default: + throw new ResourceProviderException($"The resource type {resourcePath.ResourceTypeInstances.Last().ResourceType} is not supported by the {_name} resource provider.", + StatusCodes.Status400BadRequest); + }; + } + + #region Helpers for DeleteResourceAsync + + private async Task DeleteAssistantUserContext(List instances) + { + if (_resourceReferences.TryGetValue(instances.Last().ResourceId!, out var resourceReference) + && !resourceReference.Deleted) + { + resourceReference.Deleted = true; + + await _storageService.WriteFileAsync( + _storageContainerName, + RESOURCE_REFERENCES_FILE_PATH, + JsonSerializer.Serialize(ResourceReferenceStore.FromDictionary(_resourceReferences.ToDictionary())), + default, + default); + } + else + throw new ResourceProviderException($"Could not locate the {instances.Last().ResourceId} agent resource.", + StatusCodes.Status404NotFound); + } + + #endregion + + #endregion + } +} diff --git a/src/dotnet/AzureOpenAI/ResourceProviders/DependencyInjection.cs b/src/dotnet/AzureOpenAI/ResourceProviders/DependencyInjection.cs new file mode 100644 index 0000000000..debe0e0116 --- /dev/null +++ b/src/dotnet/AzureOpenAI/ResourceProviders/DependencyInjection.cs @@ -0,0 +1,42 @@ +using FoundationaLLM.AzureOpenAI.ResourceProviders; +using FoundationaLLM.Common.Constants.Configuration; +using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Models.Configuration.Instance; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace FoundationaLLM +{ + /// + /// Provides extension methods used to configure dependency injection. + /// + public static partial class DependencyInjection + { + /// + /// Register the handler as a hosted service, passing the step name to the handler ctor + /// + /// The application builder. + /// + /// Requires an service to be also registered with the dependency injection container. + /// + public static void AddAzureOpenAIResourceProvider(this IHostApplicationBuilder builder) + { + builder.AddAzureOpenAIResourceProviderStorage(); + + builder.Services.AddSingleton(sp => + new AzureOpenAIResourceProviderService( + sp.GetRequiredService>(), + sp.GetRequiredService(), + sp.GetRequiredService>() + .Single(s => s.InstanceName == DependencyInjectionKeys.FoundationaLLM_ResourceProviders_AzureOpenAI), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp, + sp.GetRequiredService>())); + + builder.Services.ActivateSingleton(); + } + } +} diff --git a/src/dotnet/Common/Constants/Agents/AgentCapabilityCategoryNames.cs b/src/dotnet/Common/Constants/Agents/AgentCapabilityCategoryNames.cs new file mode 100644 index 0000000000..126e924b43 --- /dev/null +++ b/src/dotnet/Common/Constants/Agents/AgentCapabilityCategoryNames.cs @@ -0,0 +1,13 @@ +namespace FoundationaLLM.Common.Constants.Agents +{ + /// + /// Contains constants for the agent capability names. + /// + public static class AgentCapabilityCategoryNames + { + /// + /// Indicates the agent has the OpenAI Assistants API capability. + /// + public const string OpenAIAssistants = "OpenAI.Assistants"; + } +} diff --git a/src/dotnet/Common/Constants/Agents/CompletionRequestObjectsKeys.cs b/src/dotnet/Common/Constants/Agents/CompletionRequestObjectsKeys.cs index 9051a9eeef..9b70b46145 100644 --- a/src/dotnet/Common/Constants/Agents/CompletionRequestObjectsKeys.cs +++ b/src/dotnet/Common/Constants/Agents/CompletionRequestObjectsKeys.cs @@ -13,6 +13,16 @@ public static class CompletionRequestObjectsKeys /// public const string AllAgents = "AllAgents"; + /// + /// The key name for the OpenAI assistant identifier value. + /// + public const string OpenAIAssistantId = "OpenAI.AssistantId"; + + /// + /// The key name for the OpenAI assistant thread identifier value. + /// + public const string OpenAIAssistantThreadId = "OpenAI.AssistantThreadId"; + /// /// All completion request objects dictionary keys. /// diff --git a/src/dotnet/Common/Constants/Authorization/AuthorizableActionNames.cs b/src/dotnet/Common/Constants/Authorization/AuthorizableActionNames.cs index c4abc59f71..5e7f068063 100644 --- a/src/dotnet/Common/Constants/Authorization/AuthorizableActionNames.cs +++ b/src/dotnet/Common/Constants/Authorization/AuthorizableActionNames.cs @@ -48,6 +48,25 @@ public static class AuthorizableActionNames #endregion + #region AzureOpenAI + + /// + /// Read assistant user contexts. + /// + public const string FoundationaLLM_AzureOpenAI_AssistantUserContexts_Read = "FoundationaLLM.AzureOpenAI/assistantUserContexts/read"; + + /// + /// Create or update assistant user contexts. + /// + public const string FoundationaLLM_AzureOpenAI_AssistantUserContexts_Write = "FoundationaLLM.AzureOpenAI/assistantUserContexts/write"; + + /// + /// Delete assistant user contexts. + /// + public const string FoundationaLLM_AzureOpenAI_AssistantUserContexts_Delete = "FoundationaLLM.AzureOpenAI/assistantUserContexts/delete"; + + #endregion + #region Configuration /// @@ -81,19 +100,19 @@ public static class AuthorizableActionNames public const string FoundationaLLM_Configuration_KeyVaultSecrets_Delete = "FoundationaLLM.Configuration/keyVaultSecrets/delete"; /// - /// Read API endpoints. + /// Read API endpoint configurations. /// - public const string FoundationaLLM_Configuration_APIEndpoints_Read = "FoundationaLLM.Configuration/apiEndpointConfigurations/read"; + public const string FoundationaLLM_Configuration_APIEndpointConfigurations_Read = "FoundationaLLM.Configuration/apiEndpointConfigurations/read"; /// - /// Create or update API endpoints. + /// Create or update API endpoint configurations. /// - public const string FoundationaLLM_Configuration_APIEndpoints_Write = "FoundationaLLM.Configuration/apiEndpointConfigurations/write"; + public const string FoundationaLLM_Configuration_APIEndpointConfigurations_Write = "FoundationaLLM.Configuration/apiEndpointConfigurations/write"; /// - /// Delete API endpoints. + /// Delete API endpoint configurations. /// - public const string FoundationaLLM_Configuration_APIEndpoints_Delete = "FoundationaLLM.Configuration/apiEndpointConfigurations/delete"; + public const string FoundationaLLM_Configuration_APIEndpoinConfigurations_Delete = "FoundationaLLM.Configuration/apiEndpointConfigurations/delete"; #endregion @@ -285,17 +304,17 @@ public static class AuthorizableActionNames #region AIModel /// - /// Read aiModels models + /// Read AI models /// public const string FoundationaLLM_AIModel_AIModels_Read = "FoundationaLLM.AIModel/aiModels/read"; /// - /// Create or update models. + /// Create or update AI models. /// public const string FoundationaLLM_AIModel_AIModels_Write = "FoundationaLLM.AIModel/aiModels/write"; /// - /// Delete models. + /// Delete AI models. /// public const string FoundationaLLM_AIModel_AIModels_Delete = "FoundationaLLM.AIModel/aiModels/delete"; diff --git a/src/dotnet/Common/Constants/Configuration/AppConfigurationKeyFilters.cs b/src/dotnet/Common/Constants/Configuration/AppConfigurationKeyFilters.cs index 3f62bd3068..efe287b8bd 100644 --- a/src/dotnet/Common/Constants/Configuration/AppConfigurationKeyFilters.cs +++ b/src/dotnet/Common/Constants/Configuration/AppConfigurationKeyFilters.cs @@ -42,6 +42,12 @@ public static partial class AppConfigurationKeyFilters public const string FoundationaLLM_ResourceProviders_Attachment_Storage = "FoundationaLLM:ResourceProviders:Attachment:Storage:*"; + /// + /// Filter for the configuration section used to identify the storage settings for the FoundationaLLM.AzureOpenAI resource provider. + /// + public const string FoundationaLLM_ResourceProviders_AzureOpenAI_Storage = + "FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:*"; + /// /// Filter for the configuration section used to identify the storage settings for the FoundationaLLM.Configuration resource provider. /// diff --git a/src/dotnet/Common/Constants/Configuration/AppConfigurationKeySections.cs b/src/dotnet/Common/Constants/Configuration/AppConfigurationKeySections.cs index 523872d68d..72a2f08194 100644 --- a/src/dotnet/Common/Constants/Configuration/AppConfigurationKeySections.cs +++ b/src/dotnet/Common/Constants/Configuration/AppConfigurationKeySections.cs @@ -42,6 +42,12 @@ public static partial class AppConfigurationKeySections public const string FoundationaLLM_ResourceProviders_Attachment_Storage = "FoundationaLLM:ResourceProviders:Attachment:Storage"; + /// + /// Configuration section used to identify the storage settings for the FoundationaLLM.AzureOpenAI resource provider. + /// + public const string FoundationaLLM_ResourceProviders_AzureOpenAI_Storage = + "FoundationaLLM:ResourceProviders:AzureOpenAI:Storage"; + /// /// Configuration section used to identify the storage settings for the FoundationaLLM.Configuration resource provider. /// diff --git a/src/dotnet/Common/Constants/Configuration/AppConfigurationKeys.cs b/src/dotnet/Common/Constants/Configuration/AppConfigurationKeys.cs index 6bd8756ecb..04a7c3add5 100644 --- a/src/dotnet/Common/Constants/Configuration/AppConfigurationKeys.cs +++ b/src/dotnet/Common/Constants/Configuration/AppConfigurationKeys.cs @@ -107,6 +107,28 @@ public static class AppConfigurationKeys #endregion + #region FoundationaLLM:ResourceProviders:AzureOpenAI + + #endregion + + #region FoundationaLLM:ResourceProviders:AzureOpenAI:Storage + + /// + /// The app configuration key for the FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AuthenticationType setting. + /// Value description:
The type of authentication used to connect to the Azure Blob Storage account used by the FoundationaLLM.AzureOpenAI resource provider. Can be one of: AzureIdentity, AccountKey, or ConnectionString.
+ ///
+ public const string FoundationaLLM_ResourceProviders_AzureOpenAI_Storage_AuthenticationType = + "FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AuthenticationType"; + + /// + /// The app configuration key for the FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AccountName setting. + /// Value description:
The name of the Azure Blob Storage account used by the FoundationaLLM.AzureOpenAI resource provider.
+ ///
+ public const string FoundationaLLM_ResourceProviders_AzureOpenAI_Storage_AccountName = + "FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AccountName"; + + #endregion + #region FoundationaLLM:ResourceProviders:Configuration #endregion diff --git a/src/dotnet/Common/Constants/Configuration/DependencyInjection.cs b/src/dotnet/Common/Constants/Configuration/DependencyInjection.cs index e0096e0f69..8d6f70a8a0 100644 --- a/src/dotnet/Common/Constants/Configuration/DependencyInjection.cs +++ b/src/dotnet/Common/Constants/Configuration/DependencyInjection.cs @@ -96,6 +96,31 @@ public static void AddAttachmentResourceProviderStorage(this IHostApplicationBui }); } + /// + /// Add the named implementation for the FoundationaLLM.AzureOpenAI resource provider. + /// + /// The application builder. + public static void AddAzureOpenAIResourceProviderStorage(this IHostApplicationBuilder builder) + { + builder.Services.AddOptions( + DependencyInjectionKeys.FoundationaLLM_ResourceProviders_AzureOpenAI) + .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_ResourceProviders_AzureOpenAI_Storage)); + + builder.Services.AddSingleton(sp => + { + var settings = sp.GetRequiredService>() + .Get(DependencyInjectionKeys.FoundationaLLM_ResourceProviders_AzureOpenAI); + var logger = sp.GetRequiredService>(); + + return new BlobStorageService( + Options.Create(settings), + logger) + { + InstanceName = DependencyInjectionKeys.FoundationaLLM_ResourceProviders_AzureOpenAI + }; + }); + } + /// /// Add the named implementation for the FoundationaLLM.Configuration resource provider. /// diff --git a/src/dotnet/Common/Constants/Configuration/DependencyInjectionKeys.cs b/src/dotnet/Common/Constants/Configuration/DependencyInjectionKeys.cs index de54675592..e7e94d54a8 100644 --- a/src/dotnet/Common/Constants/Configuration/DependencyInjectionKeys.cs +++ b/src/dotnet/Common/Constants/Configuration/DependencyInjectionKeys.cs @@ -30,6 +30,12 @@ public static partial class DependencyInjectionKeys public const string FoundationaLLM_ResourceProviders_Attachment = "FoundationaLLM:ResourceProviders:Attachment"; + /// + /// Dependency injection key used by the FoundationaLLM.AzureOpenAI resource provider. + /// + public const string FoundationaLLM_ResourceProviders_AzureOpenAI = + "FoundationaLLM:ResourceProviders:AzureOpenAI"; + /// /// Dependency injection key used by the FoundationaLLM.Configuration resource provider. /// diff --git a/src/dotnet/Common/Constants/Data/AppConfiguration.json b/src/dotnet/Common/Constants/Data/AppConfiguration.json index 0787752cf2..a20f56e1c0 100644 --- a/src/dotnet/Common/Constants/Data/AppConfiguration.json +++ b/src/dotnet/Common/Constants/Data/AppConfiguration.json @@ -140,6 +140,39 @@ } ] }, + { + "namespace": "ResourceProviders:AzureOpenAI", + "dependency_injection_key": { + "description": "Dependency injection key used by the FoundationaLLM.AzureOpenAI resource provider." + }, + "configuration_section": null, + "configuration_keys": [] + }, + { + "namespace": "ResourceProviders:AzureOpenAI:Storage", + "dependency_injection_key": null, + "configuration_section": { + "description": "Configuration section used to identify the storage settings for the FoundationaLLM.AzureOpenAI resource provider." + }, + "configuration_keys": [ + { + "name": "AuthenticationType", + "description": "The type of authentication used to connect to the Azure Blob Storage account used by the FoundationaLLM.AzureOpenAI resource provider. Can be one of: AzureIdentity, AccountKey, or ConnectionString.", + "secret": "", + "value": "AzureIdentity", + "content_type": "", + "first_version": "0.8.0" + }, + { + "name": "AccountName", + "description": "The name of the Azure Blob Storage account used by the FoundationaLLM.AzureOpenAI resource provider.", + "secret": "", + "value": "${env:AZURE_STORAGE_ACCOUNT_NAME}", + "content_type": "", + "first_version": "0.8.0" + } + ] + }, { "namespace": "ResourceProviders:Configuration", "dependency_injection_key": { diff --git a/src/dotnet/Common/Constants/EventSetEventNamespaces.cs b/src/dotnet/Common/Constants/EventSetEventNamespaces.cs index 559b46fc96..d911bb46f2 100644 --- a/src/dotnet/Common/Constants/EventSetEventNamespaces.cs +++ b/src/dotnet/Common/Constants/EventSetEventNamespaces.cs @@ -40,5 +40,10 @@ public static class EventSetEventNamespaces /// The namespace name for events concerning the FoundationaLLM.AIModel resource provider. ///
public const string FoundationaLLM_ResourceProvider_AIModel = "ResourceProvider.FoundationaLLM.AIModel"; + + /// + /// The namespace name for events concerning the FoundationaLLM.AzureOpenAI resource provider. + /// + public const string FoundationaLLM_ResourceProvider_AzureOpenAI = "ResourceProvider.FoundationaLLM.AzureOpenAI"; } } diff --git a/src/dotnet/Common/Constants/OpenAI/OpenAIAgentCapabilityParameterNames.cs b/src/dotnet/Common/Constants/OpenAI/OpenAIAgentCapabilityParameterNames.cs new file mode 100644 index 0000000000..11fc379586 --- /dev/null +++ b/src/dotnet/Common/Constants/OpenAI/OpenAIAgentCapabilityParameterNames.cs @@ -0,0 +1,48 @@ +using FoundationaLLM.Common.Interfaces; + +namespace FoundationaLLM.Common.Constants.OpenAI +{ + /// + /// Provides the names of the parameters that can be used to create OpenAI agent capabilities. + /// + /// + /// The constants are used by the callers of the implementations. + /// + public static class OpenAIAgentCapabilityParameterNames + { + /// + /// Indicates the need to create a new OpenAI assistant. + /// + public const string CreateAssistant = "OpenAI.Assistants.CreateAssistant"; + + /// + /// Indicates the need to create a new OpenAI assistant thread. + /// + public const string CreateAssistantThread = "OpenAI.Assistants.CreateAssistantThread"; + + /// + /// Provides the identifier of an existing OpenAI assistant. + /// + public const string AssistantId = "OpenAI.Assistants.AssistantId"; + + /// + /// Provides the prompt used by the OpenAI assistant. + /// + public const string AssistantPrompt = "OpenAI.Assistants.AssistantPrompt"; + + /// + /// Provides the identifier of an existing OpenAI assistant thread. + /// + public const string AssistantThreadId = "OpenAI.Assistants.AssistantThreadId"; + + /// + /// Provides the Azure OpenAI endpoint used to manage Open AI assistants. + /// + public const string Endpoint = "OpenAI.Assistants.Endpoint"; + + /// + /// Provides the model deployment name used by the OpenAI assistant. + /// + public const string ModelDeploymentName = "OpenAI.Assistants.ModelDeploymentName"; + } +} diff --git a/src/dotnet/Common/Constants/ResourceProviders/AIModelResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/AIModelResourceProviderActions.cs deleted file mode 100644 index 387f4d72df..0000000000 --- a/src/dotnet/Common/Constants/ResourceProviders/AIModelResourceProviderActions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FoundationaLLM.Common.Constants.ResourceProviders -{ - /// - /// The names of the actions implemented by the AIModel resource provider. - /// - public class AIModelResourceProviderActions - { - /// - /// Check the validity of a resource name. - /// - public const string CheckName = "checkname"; - /// - /// Purges a soft-deleted resource. - /// - public const string Purge = "purge"; - - } -} diff --git a/src/dotnet/Common/Constants/ResourceProviders/AIModelResourceProviderMetadata.cs b/src/dotnet/Common/Constants/ResourceProviders/AIModelResourceProviderMetadata.cs index 00de79432a..228ff54529 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/AIModelResourceProviderMetadata.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/AIModelResourceProviderMetadata.cs @@ -24,10 +24,10 @@ public static class AIModelResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), ], Actions = [ - new ResourceTypeAction(DataSourceResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]), - new ResourceTypeAction(DataSourceResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] diff --git a/src/dotnet/Common/Constants/ResourceProviders/AgentResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/AgentResourceProviderActions.cs deleted file mode 100644 index 565354914c..0000000000 --- a/src/dotnet/Common/Constants/ResourceProviders/AgentResourceProviderActions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FoundationaLLM.Common.Constants.ResourceProviders -{ - /// - /// The names of the actions implemented by the Agent resource provider. - /// - public static class AgentResourceProviderActions - { - /// - /// Check the validity of a resource name. - /// - public const string CheckName = "checkname"; - /// - /// Purges a soft-deleted resource. - /// - public const string Purge = "purge"; - } -} diff --git a/src/dotnet/Common/Constants/ResourceProviders/AgentResourceProviderMetadata.cs b/src/dotnet/Common/Constants/ResourceProviders/AgentResourceProviderMetadata.cs index 63db41646c..3897ba4cb2 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/AgentResourceProviderMetadata.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/AgentResourceProviderMetadata.cs @@ -24,10 +24,10 @@ public static class AgentResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), ], Actions = [ - new ResourceTypeAction(AgentResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]), - new ResourceTypeAction(AgentResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] diff --git a/src/dotnet/Common/Constants/ResourceProviders/AttachmentResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/AttachmentResourceProviderActions.cs deleted file mode 100644 index cef63da556..0000000000 --- a/src/dotnet/Common/Constants/ResourceProviders/AttachmentResourceProviderActions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FoundationaLLM.Common.Constants.ResourceProviders -{ - /// - /// The names of the actions implemented by the Attachment resource provider. - /// - public class AttachmentResourceProviderActions - { - - } -} diff --git a/src/dotnet/Common/Constants/ResourceProviders/AuthorizationResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/AuthorizationResourceProviderActions.cs deleted file mode 100644 index bb20c6c755..0000000000 --- a/src/dotnet/Common/Constants/ResourceProviders/AuthorizationResourceProviderActions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace FoundationaLLM.Common.Constants.ResourceProviders -{ - /// - /// The names of the actions implemented by the Authorization resource provider. - /// - public static class AuthorizationResourceProviderActions - { - /// - /// Filter role assignments. - /// - public const string Filter = "filter"; - } -} diff --git a/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAIResourceProviderMetadata.cs b/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAIResourceProviderMetadata.cs new file mode 100644 index 0000000000..c7723e6c53 --- /dev/null +++ b/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAIResourceProviderMetadata.cs @@ -0,0 +1,38 @@ +using FoundationaLLM.Common.Models.ResourceProviders; +using FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI; + +namespace FoundationaLLM.Common.Constants.ResourceProviders +{ + /// + /// Provides metadata for the FoundationaLLM.AzureOpenAI resource provider. + /// + public static class AzureOpenAIResourceProviderMetadata + { + /// + /// The metadata describing the resource types allowed by the resource provider. + /// + public static Dictionary AllowedResourceTypes => new() + { + { + AzureOpenAIResourceTypeNames.AssistantUserContext, + new ResourceTypeDescriptor( + AzureOpenAIResourceTypeNames.AssistantUserContext) + { + AllowedTypes = [ + new ResourceTypeAllowedTypes(HttpMethod.Get.Method, [], [], [typeof(ResourceProviderGetResult)]), + new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(AssistantUserContext)], [typeof(AssistantUserContextUpsertResult)]), + new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), + ], + Actions = [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) + ]), + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ + new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) + ]) + ] + } + } + }; + } +} diff --git a/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAIResourceTypeNames.cs b/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAIResourceTypeNames.cs new file mode 100644 index 0000000000..a5e2bb3324 --- /dev/null +++ b/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAIResourceTypeNames.cs @@ -0,0 +1,13 @@ +namespace FoundationaLLM.Common.Constants.ResourceProviders +{ + /// + /// Contains constants of the names of the resource types managed by the FoundationaLLM.AzureOpenAI resource provider. + /// + public class AzureOpenAIResourceTypeNames + { + /// + /// Prompts. + /// + public const string AssistantUserContext = "assistantUserContexts"; + } +} diff --git a/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAITypes.cs b/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAITypes.cs new file mode 100644 index 0000000000..728e9f09a0 --- /dev/null +++ b/src/dotnet/Common/Constants/ResourceProviders/AzureOpenAITypes.cs @@ -0,0 +1,13 @@ +namespace FoundationaLLM.Common.Constants.ResourceProviders +{ + /// + /// Contains constants for the types of resources managed by the FoundationaLLM.AzureOpenAI resource provider. + /// + public static class AzureOpenAITypes + { + /// + /// User context associated resources related to OpenAI assistants. + /// + public const string AssistantUserContext = "assistant-user-context"; + } +} diff --git a/src/dotnet/Common/Constants/ResourceProviders/ConfigurationResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/ConfigurationResourceProviderActions.cs deleted file mode 100644 index 6e3a2e3fac..0000000000 --- a/src/dotnet/Common/Constants/ResourceProviders/ConfigurationResourceProviderActions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace FoundationaLLM.Common.Constants.ResourceProviders -{ - /// - /// The names of the actions implemented by the FoundationaLLM.Configuration resource provider. - /// - public static class ConfigurationResourceProviderActions - { - /// - /// Check the validity of a resource name. - /// - public const string CheckName = "checkname"; - } -} diff --git a/src/dotnet/Common/Constants/ResourceProviders/ConfigurationResourceProviderMetadata.cs b/src/dotnet/Common/Constants/ResourceProviders/ConfigurationResourceProviderMetadata.cs index ab703f8156..118582db4e 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/ConfigurationResourceProviderMetadata.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/ConfigurationResourceProviderMetadata.cs @@ -25,7 +25,7 @@ public static class ConfigurationResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), ], Actions = [ - new ResourceTypeAction(ConfigurationResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]) ] diff --git a/src/dotnet/Common/Constants/ResourceProviders/DataSourceResourceProviderMetadata.cs b/src/dotnet/Common/Constants/ResourceProviders/DataSourceResourceProviderMetadata.cs index f090f993cb..82c92e6d05 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/DataSourceResourceProviderMetadata.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/DataSourceResourceProviderMetadata.cs @@ -24,13 +24,13 @@ public static class DataSourceResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), ], Actions = [ - new ResourceTypeAction(DataSourceResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]), - new ResourceTypeAction(DataSourceResourceProviderActions.Filter, false, true, [ + new ResourceTypeAction(ResourceProviderActions.Filter, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceFilter)], [typeof(DataSourceBase)]) ]), - new ResourceTypeAction(DataSourceResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] diff --git a/src/dotnet/Common/Constants/ResourceProviders/PromptResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/PromptResourceProviderActions.cs deleted file mode 100644 index 86910d2f1d..0000000000 --- a/src/dotnet/Common/Constants/ResourceProviders/PromptResourceProviderActions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FoundationaLLM.Common.Constants.ResourceProviders -{ - /// - /// The names of the actions implemented by the Agent resource provider. - /// - public static class PromptResourceProviderActions - { - /// - /// Check the validity of a resource name. - /// - public const string CheckName = "checkname"; - /// - /// Purges a soft-deleted resource. - /// - public const string Purge = "purge"; - } -} diff --git a/src/dotnet/Common/Constants/ResourceProviders/PromptResourceProviderMetadata.cs b/src/dotnet/Common/Constants/ResourceProviders/PromptResourceProviderMetadata.cs index bed59c1afb..ae49365bda 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/PromptResourceProviderMetadata.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/PromptResourceProviderMetadata.cs @@ -27,7 +27,7 @@ public static class PromptResourceProviderMetadata new ResourceTypeAction("checkname", false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]), - new ResourceTypeAction(PromptResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] diff --git a/src/dotnet/Common/Constants/ResourceProviders/DataSourceResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/ResourceProviderActions.cs similarity index 72% rename from src/dotnet/Common/Constants/ResourceProviders/DataSourceResourceProviderActions.cs rename to src/dotnet/Common/Constants/ResourceProviders/ResourceProviderActions.cs index 32af3aae18..a095b55dbc 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/DataSourceResourceProviderActions.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/ResourceProviderActions.cs @@ -1,21 +1,23 @@ namespace FoundationaLLM.Common.Constants.ResourceProviders { /// - /// The names of the actions implemented by the Data Source resource provider. + /// The names of the actions implemented by most of the FoundationaLLM resource providers. /// - public class DataSourceResourceProviderActions + public static class ResourceProviderActions { /// /// Check the validity of a resource name. /// public const string CheckName = "checkname"; - /// - /// Apply a filter for data source retrieval. - /// - public const string Filter = "filter"; + /// /// Purges a soft-deleted resource. /// public const string Purge = "purge"; + + /// + /// Filter resources. + /// + public const string Filter = "filter"; } } diff --git a/src/dotnet/Common/Constants/ResourceProviders/ResourceProviderNames.cs b/src/dotnet/Common/Constants/ResourceProviders/ResourceProviderNames.cs index 0a127d2328..00b212d201 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/ResourceProviderNames.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/ResourceProviderNames.cs @@ -47,6 +47,11 @@ public static class ResourceProviderNames /// public const string FoundationaLLM_AIModel = "FoundationaLLM.AIModel"; + /// + /// The name of the FoundationaLLM.AzureOpenAI resource provider. + /// + public const string FoundationaLLM_AzureOpenAI = "FoundationaLLM.AzureOpenAI"; + /// /// Contains all the resource provider names. /// @@ -58,6 +63,8 @@ public static class ResourceProviderNames FoundationaLLM_DataSource, FoundationaLLM_Attachment, FoundationaLLM_Authorization, - FoundationaLLM_AIModel]; + FoundationaLLM_AIModel, + FoundationaLLM_AzureOpenAI + ]; } } diff --git a/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderActions.cs b/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderActions.cs index a6d357ea44..6fd8874aa0 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderActions.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderActions.cs @@ -5,16 +5,6 @@ /// public static class VectorizationResourceProviderActions { - /// - /// Check the validity of a resource name. - /// - public const string CheckName = "checkname"; - - /// - /// Apply a filter for vectorization resource retrieval. - /// - public const string Filter = "filter"; - /// /// Activate a vectorization pipeline. /// @@ -29,10 +19,5 @@ public static class VectorizationResourceProviderActions /// Process a vectorization request. /// public const string Process = "process"; - - /// - /// Purges a soft-deleted resource. - /// - public const string Purge = "purge"; } } diff --git a/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderMetadata.cs b/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderMetadata.cs index 24c9669887..6958576384 100644 --- a/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderMetadata.cs +++ b/src/dotnet/Common/Constants/ResourceProviders/VectorizationResourceProviderMetadata.cs @@ -31,7 +31,7 @@ public static class VectorizationResourceProviderMetadata new ResourceTypeAction(VectorizationResourceProviderActions.Deactivate, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(VectorizationResult)]) ]), - new ResourceTypeAction(VectorizationResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] @@ -65,10 +65,10 @@ public static class VectorizationResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), ], Actions = [ - new ResourceTypeAction(VectorizationResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]), - new ResourceTypeAction(VectorizationResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] @@ -85,10 +85,10 @@ public static class VectorizationResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), ], Actions = [ - new ResourceTypeAction(VectorizationResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]), - new ResourceTypeAction(VectorizationResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] @@ -105,13 +105,13 @@ public static class VectorizationResourceProviderMetadata new ResourceTypeAllowedTypes(HttpMethod.Delete.Method, [], [], []), ], Actions = [ - new ResourceTypeAction(VectorizationResourceProviderActions.CheckName, false, true, [ + new ResourceTypeAction(ResourceProviderActions.CheckName, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceName)], [typeof(ResourceNameCheckResult)]) ]), - new ResourceTypeAction(VectorizationResourceProviderActions.Filter, false, true, [ + new ResourceTypeAction(ResourceProviderActions.Filter, false, true, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [typeof(ResourceFilter)], [typeof(IndexingProfile)]) ]), - new ResourceTypeAction(VectorizationResourceProviderActions.Purge, true, false, [ + new ResourceTypeAction(ResourceProviderActions.Purge, true, false, [ new ResourceTypeAllowedTypes(HttpMethod.Post.Method, [], [], [typeof(ResourceProviderActionResult)]) ]) ] diff --git a/src/dotnet/Common/Extensions/ResourceProviderServiceExtensions.cs b/src/dotnet/Common/Extensions/ResourceProviderServiceExtensions.cs index 86a6b4a8ea..26f4e34f4f 100644 --- a/src/dotnet/Common/Extensions/ResourceProviderServiceExtensions.cs +++ b/src/dotnet/Common/Extensions/ResourceProviderServiceExtensions.cs @@ -1,7 +1,9 @@ -using FoundationaLLM.Common.Exceptions; +using FoundationaLLM.Common.Constants.ResourceProviders; +using FoundationaLLM.Common.Exceptions; using FoundationaLLM.Common.Interfaces; using FoundationaLLM.Common.Models.Authentication; using FoundationaLLM.Common.Models.ResourceProviders; +using System.Text.Json; namespace FoundationaLLM.Common.Extensions { @@ -10,6 +12,69 @@ namespace FoundationaLLM.Common.Extensions /// public static class ResourceProviderServiceExtensions { + /// + /// Creates or updates a resource. + /// + /// The object type of the resource being created or updated. + /// The object type of the response returned by the operation + /// The providing the resource provider services. + /// The FoundationaLLM instance ID. + /// The resource object. + /// The name of the resource type. + /// The providing information about the calling user identity. + /// A object with the result of the operation. + /// + public static async Task CreateOrUpdateResource( + this IResourceProviderService resourceProviderService, + string instanceId, + T resource, + string resourceTypeName, + UnifiedUserIdentity userIdentity) + where T : ResourceBase + where TResult : ResourceProviderUpsertResult + { + if (!resourceProviderService.IsInitialized) + throw new ResourceProviderException($"The resource provider {resourceProviderService.Name} is not initialized."); + + var result = await resourceProviderService.HandlePostAsync( + $"/instances/{instanceId}/providers/{resourceProviderService.Name}/{resourceTypeName}/{resource.Name}", + JsonSerializer.Serialize(resource), + userIdentity); + + return (result as TResult)!; + } + + /// + /// Gets a resource from the resource provider service. + /// + /// The object type of the resource being retrieved. + /// The providing the resource provider services. + /// The FoundationaLLM instance ID. + /// The resource name being checked. + /// The name of the resource type. + /// The providing information about the calling user identity. + /// A resource object of type . + public static async Task GetResource( + this IResourceProviderService resourceProviderService, + string instanceId, + string resourceName, + string resourceTypeName, + UnifiedUserIdentity userIdentity) + where T : ResourceBase + { + if (!resourceProviderService.IsInitialized) + throw new ResourceProviderException($"The resource provider {resourceProviderService.Name} is not initialized."); + + var result = await resourceProviderService.HandleGetAsync( + $"/instances/{instanceId}/providers/{resourceProviderService.Name}/{resourceTypeName}/{resourceName}", + userIdentity) as List>; + + if (result == null || result.Count == 0) + throw new ResourceProviderException($"The resource provider {resourceProviderService.Name} is unable to retrieve the {resourceName} resource."); + + return result.First().Resource; + } + /// /// Gets a resource from the resource provider service. /// @@ -93,6 +158,63 @@ public static async Task>> GetResourcesWithRBA return (result as List>)!; } + /// + /// Checks a resource name for availability. + /// + /// The providing the resource provider services. + /// The FoundationaLLM instance ID. + /// The resource name being checked. + /// The name of the resource type. + /// The providing information about the calling user identity. + /// A object with the result of the name check. + public static async Task CheckResourceName( + this IResourceProviderService resourceProviderService, + string instanceId, + string resourceName, + string resourceTypeName, + UnifiedUserIdentity userIdentity) + { + if (!resourceProviderService.IsInitialized) + throw new ResourceProviderException($"The resource provider {resourceProviderService.Name} is not initialized."); + + var result = await resourceProviderService.HandlePostAsync( + $"/instances/{instanceId}/providers/{resourceProviderService.Name}/{resourceTypeName}/{ResourceProviderActions.CheckName}", + JsonSerializer.Serialize(new ResourceName { Name = resourceName }), + userIdentity); + + return (result as ResourceNameCheckResult)!; + } + + /// + /// Checks if a resource exists. + /// + /// The providing the resource provider services. + /// The FoundationaLLM instance ID. + /// The resource name being checked. + /// The name of the resource type. + /// The providing information about the calling user identity. + /// True if the resource exists, False otherwise. + /// + /// If a resource was logically deleted but not purged, this method will return True, indicating the existence of the resource. + /// + public static async Task ResourceExists( + this IResourceProviderService resourceProviderService, + string instanceId, + string resourceName, + string resourceTypeName, + UnifiedUserIdentity userIdentity) + { + if (!resourceProviderService.IsInitialized) + throw new ResourceProviderException($"The resource provider {resourceProviderService.Name} is not initialized."); + + var result = await resourceProviderService.HandlePostAsync( + $"/instances/{instanceId}/providers/{resourceProviderService.Name}/{resourceTypeName}/{ResourceProviderActions.CheckName}", + JsonSerializer.Serialize(new ResourceName { Name = resourceName }), + userIdentity); + + return (result as ResourceNameCheckResult)!.Status == NameCheckResultType.Denied; + } + /// /// Waits for the resource provider service to be initialized. /// diff --git a/src/dotnet/Common/Extensions/StringExtensions.cs b/src/dotnet/Common/Extensions/StringExtensions.cs index e0423931df..cfdc67bc90 100644 --- a/src/dotnet/Common/Extensions/StringExtensions.cs +++ b/src/dotnet/Common/Extensions/StringExtensions.cs @@ -1,9 +1,5 @@ using System.Security.Cryptography; using System.Text; -using FoundationaLLM.Common.Exceptions; -using FoundationaLLM.Common.Interfaces; -using FoundationaLLM.Common.Models.Authentication; -using FoundationaLLM.Common.Models.ResourceProviders; namespace FoundationaLLM.Common.Extensions { @@ -12,30 +8,17 @@ namespace FoundationaLLM.Common.Extensions /// public static class StringExtensions { - /// - /// Generates a valid resource name from the provided name. - /// A good example use for this method is generating a valid - /// resource name from an uploaded file name. + /// Converts a UPN (User Principal Name) to a string that is better suited to be used as an identifier. /// - /// The original resource name to convert. - /// - public static string GenerateValidResourceName( - this string name) - { - var hash = SHA256.HashData(Encoding.UTF8.GetBytes(name)); - var base64Hash = Convert.ToBase64String(hash); - - // Replace invalid characters. - base64Hash = base64Hash.Replace("+", "-") - .Replace("/", "_") - .Replace("=", ""); // Remove padding characters. - - // Prefix with a letter to ensure it starts with a letter. - var validName = "a" + base64Hash; - - // Ensure the name length is within a reasonable limit, e.g., 50 characters. - return validName.Length > 50 ? validName[..50] : validName; - } + /// The original UPN (User Principal Name). + /// A string containing the normalized UPN (User Principal Name). + public static string NormalizeUserPrincipalName( + this string upn) => + upn + .Replace('.', '_') + .Replace("#", ".") + .Replace("@", "_") + .ToLower(); } } diff --git a/src/dotnet/Common/Interfaces/IGatewayServiceClient.cs b/src/dotnet/Common/Interfaces/IGatewayServiceClient.cs new file mode 100644 index 0000000000..55448ad807 --- /dev/null +++ b/src/dotnet/Common/Interfaces/IGatewayServiceClient.cs @@ -0,0 +1,45 @@ +using FoundationaLLM.Common.Constants.OpenAI; +using FoundationaLLM.Common.Models.Vectorization; + +namespace FoundationaLLM.Common.Interfaces +{ + /// + /// Defines the interface of the FoundationaLLM Gateway service client. + /// + public interface IGatewayServiceClient + { + /// + /// Starts an embedding operation. + /// + /// The FoundationaLLM instance id. + /// The object containing the details of the embedding operation. + /// A object with the outcome of the operation. + Task StartEmbeddingOperation(string instanceId, TextEmbeddingRequest embeddingRequest); + + /// + /// Retrieves the outcome of an embedding operation. + /// + /// The FoundationaLLM instance id. + /// The unique identifier of the text embedding operation. + /// A object with the outcome of the operation. + Task GetEmbeddingOperationResult(string instanceId, string operationId); + + /// + /// Creates a new LLM-based agent capability. + /// + /// The FoundationaLLM instance id. + /// The category of the capability. + /// The name of the capability to be created. + /// A dictionary of parameter values used to create the capability. + /// A dictionary of output values. + /// + /// The supported categories are: + /// + /// + /// OpenAI.Assistants (the names of the keys for the parameters and output dictionaries are defined in ) + /// + /// + /// + Task> CreateAgentCapability(string instanceId, string capabilityCategory, string capabilityName, Dictionary? parameters = null); + } +} diff --git a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentBase.cs b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentBase.cs index 2ada7ba494..e3b1710af1 100644 --- a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentBase.cs +++ b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentBase.cs @@ -74,6 +74,14 @@ public class AgentBase : ResourceBase AgentTypes.AudioClassification => typeof(AudioClassificationAgent), _ => throw new ResourceProviderException($"The agent type {Type} is not supported.") }; + + /// + /// Checks whether the agent has a specified capbability. + /// + /// The name of the capability. + /// True if the agent has the capability, False otherwise. + public bool HasCapability(string capabilityName) => + Capabilities?.Contains(capabilityName) ?? false; } /// diff --git a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentCapabilities.cs b/src/dotnet/Common/Models/ResourceProviders/Agent/AgentCapabilities.cs deleted file mode 100644 index 6777fd2e82..0000000000 --- a/src/dotnet/Common/Models/ResourceProviders/Agent/AgentCapabilities.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace FoundationaLLM.Common.Models.ResourceProviders.Agent -{ - /// - /// Contains constants for the capabilities of agents. - /// - public static class AgentCapabilities - { - /// - /// Indicated the agent supports the OpenAI Assistants API capability. - /// - public const string OpenAIAssistants = "OpenAIAssistants"; - } -} diff --git a/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantConversation.cs b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantConversation.cs new file mode 100644 index 0000000000..dbc0b91593 --- /dev/null +++ b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantConversation.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI +{ + /// + /// Provides information about a conversation driven by an OpenAI assistant. + /// + public class AssistantConversation + { + /// + /// The FoundationaLLM session (conversation) id. + /// + [JsonPropertyName("session_id")] + public required string SessionId { get; set; } + + /// + /// The OpenAI thread id associated with the FoundationaLLM session (conversation) id. + /// + [JsonPropertyName("openai_thread_id")] + public string? OpenAIThreadId { get; set; } + + /// + /// The time at which the thread was created. + /// + [JsonPropertyName("openai_thread_created_on")] + public DateTimeOffset? OpenAIThreadCreatedOn { get; set; } + } +} diff --git a/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantUserContext.cs b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantUserContext.cs new file mode 100644 index 0000000000..aa9a65516c --- /dev/null +++ b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantUserContext.cs @@ -0,0 +1,69 @@ +using FoundationaLLM.Common.Constants.ResourceProviders; +using System.Text.Json.Serialization; + +namespace FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI +{ + /// + /// Provides information about the OpenAI artifacts associated with a user. + /// + public class AssistantUserContext : AzureOpenAIResourceBase + { + /// + /// Set default property values. + /// + public AssistantUserContext() => + Type = AzureOpenAITypes.AssistantUserContext; + + /// + /// The UPN (user principal name) to which the context is associated. + /// + [JsonPropertyName("user_principal_name")] + [JsonPropertyOrder(100)] + public required string UserPrincipalName { get; set; } + + /// + /// The Azure OpenAI endpoint used to manage the assistant. + /// + [JsonPropertyName("endpoint")] + [JsonPropertyOrder(101)] + public required string Endpoint { get; set; } + + /// + /// The Azure OpenAI model deployment name used by the assistant. + /// + [JsonPropertyName("model_name")] + [JsonPropertyOrder(102)] + public required string ModelDeploymentName { get; set; } + + /// + /// The time at which the assistant was created. + /// + [JsonPropertyName("prompt")] + [JsonPropertyOrder(103)] + public required string Prompt { get; set; } + + /// + /// The OpenAI identifier of the assistant. + /// + [JsonPropertyName("openai_assistant_id")] + [JsonPropertyOrder(104)] + public string? OpenAIAssistantId { get; set; } + + /// + /// The time at which the assistant was created. + /// + [JsonPropertyName("openai_assistant_created_on")] + [JsonPropertyOrder(105)] + public DateTimeOffset? OpenAIAssistantCreatedOn { get; set; } + + /// + /// The dictionary of objects providing information about the conversations driven by the OpenAI assistant. + /// + /// + /// The keys of the dictionary are the FoundationaLLM session identifiers. + /// + [JsonPropertyName("conversations")] + [JsonPropertyOrder(106)] + public Dictionary Conversations { get; set; } = []; + } +} diff --git a/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantUserContextUpsertResult.cs b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantUserContextUpsertResult.cs new file mode 100644 index 0000000000..4de9db449b --- /dev/null +++ b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AssistantUserContextUpsertResult.cs @@ -0,0 +1,18 @@ +namespace FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI +{ + /// + /// Represents the result of an upsert operation for an object. + /// + public class AssistantUserContextUpsertResult : ResourceProviderUpsertResult + { + /// + /// The identifier of the newly created OpenAI assistant (if any). + /// + public string? NewOpenAIAssistantId { get; set; } + + /// + /// The identifier of the newly created OpenAI assistant thread id (if any). + /// + public string? NewOpenAIAssistantThreadId { get; set; } + } +} diff --git a/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AzureOpenAIResourceBase.cs b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AzureOpenAIResourceBase.cs new file mode 100644 index 0000000000..92a31dbe65 --- /dev/null +++ b/src/dotnet/Common/Models/ResourceProviders/AzureOpenAI/AzureOpenAIResourceBase.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI +{ + /// + /// Basic model for resources managed by the FoundationaLLM.AzureOpenAI resource manager. + /// + [JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] + [JsonDerivedType(typeof(AssistantUserContext), "assistant-user-context")] + public class AzureOpenAIResourceBase : ResourceBase + { + /// + [JsonIgnore] + public override string? Type { get; set; } + } +} diff --git a/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs b/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs index cfa798d310..7602156f7d 100644 --- a/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs +++ b/src/dotnet/Common/Models/ResourceProviders/ResourcePath.cs @@ -170,7 +170,7 @@ public string GetObjectId( StatusCodes.Status400BadRequest); else return $"/instances/{_instanceId}/providers/{_resourceProvider}/{string.Join("/", - _resourceTypeInstances.Select(i => $"{i.ResourceType}/{i.ResourceId}").ToArray())}"; + _resourceTypeInstances.Select(i => i.ResourceId == null ? $"{i.ResourceType}" : $"{i.ResourceType}/{i.ResourceId}").ToArray())}"; } } diff --git a/src/dotnet/Common/Services/Events/AzureEventGridEventService.cs b/src/dotnet/Common/Services/Events/AzureEventGridEventService.cs index ada02343c0..022ae18343 100644 --- a/src/dotnet/Common/Services/Events/AzureEventGridEventService.cs +++ b/src/dotnet/Common/Services/Events/AzureEventGridEventService.cs @@ -50,6 +50,10 @@ public class AzureEventGridEventService : IEventService { EventSetEventNamespaces.FoundationaLLM_ResourceProvider_AIModel, null + }, + { + EventSetEventNamespaces.FoundationaLLM_ResourceProvider_AzureOpenAI, + null } }; diff --git a/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs b/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs index d1c6b74dd0..19eb166a6c 100644 --- a/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs +++ b/src/dotnet/Common/Services/ResourceProviders/ResourceProviderServiceBase.cs @@ -29,9 +29,13 @@ public class ResourceProviderServiceBase : IResourceProviderService private readonly List? _eventNamespacesToSubscribe; private readonly ImmutableList _allowedResourceProviders; private readonly Dictionary _allowedResourceTypes; - private readonly IServiceProvider _serviceProvider; private readonly Dictionary _resourceProviders = []; + /// + /// The tha provides dependency injection services. + /// + protected readonly IServiceProvider _serviceProvider; + /// /// The providing authorization services to the resource provider. /// diff --git a/src/dotnet/Common/Templates/appconfig.template.json b/src/dotnet/Common/Templates/appconfig.template.json index ec1742cc91..90af8cf2a2 100644 --- a/src/dotnet/Common/Templates/appconfig.template.json +++ b/src/dotnet/Common/Templates/appconfig.template.json @@ -63,6 +63,20 @@ "content_type": "", "tags": {} }, + { + "key": "FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AuthenticationType", + "value": "AzureIdentity", + "label": null, + "content_type": "", + "tags": {} + }, + { + "key": "FoundationaLLM:ResourceProviders:AzureOpenAI:Storage:AccountName", + "value": "${env:AZURE_STORAGE_ACCOUNT_NAME}", + "label": null, + "content_type": "", + "tags": {} + }, { "key": "FoundationaLLM:ResourceProviders:Configuration:Storage:AuthenticationType", "value": "AzureIdentity", diff --git a/src/dotnet/CoreAPI/Controllers/AttachmentsController.cs b/src/dotnet/CoreAPI/Controllers/AttachmentsController.cs index e4233e3289..746330e5a4 100644 --- a/src/dotnet/CoreAPI/Controllers/AttachmentsController.cs +++ b/src/dotnet/CoreAPI/Controllers/AttachmentsController.cs @@ -58,7 +58,7 @@ public async Task Upload(string instanceId, IFormFile file) return BadRequest("File not selected."); var fileName = file.FileName; - var name = fileName.GenerateValidResourceName(); + var name = $"a-{Guid.NewGuid()}-{DateTime.UtcNow.Ticks}"; var contentType = file.ContentType; using (var stream = file.OpenReadStream()) diff --git a/src/dotnet/DataSource/ResourceProviders/DataSourceResourceProviderService.cs b/src/dotnet/DataSource/ResourceProviders/DataSourceResourceProviderService.cs index bf2260f595..814ea6d9a0 100644 --- a/src/dotnet/DataSource/ResourceProviders/DataSourceResourceProviderService.cs +++ b/src/dotnet/DataSource/ResourceProviders/DataSourceResourceProviderService.cs @@ -275,9 +275,9 @@ protected override async Task ExecuteActionAsync(ResourcePath resourcePa { DataSourceResourceTypeNames.DataSources => resourcePath.ResourceTypeInstances.Last().Action switch { - DataSourceResourceProviderActions.CheckName => CheckDataSourceName(serializedAction), - DataSourceResourceProviderActions.Filter => await Filter(serializedAction), - DataSourceResourceProviderActions.Purge => await PurgeResource(resourcePath), + ResourceProviderActions.CheckName => CheckDataSourceName(serializedAction), + ResourceProviderActions.Filter => await Filter(serializedAction), + ResourceProviderActions.Purge => await PurgeResource(resourcePath), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, diff --git a/src/dotnet/Gateway/Client/GatewayServiceClient.cs b/src/dotnet/Gateway/Client/GatewayServiceClient.cs index c50be62246..54e653c438 100644 --- a/src/dotnet/Gateway/Client/GatewayServiceClient.cs +++ b/src/dotnet/Gateway/Client/GatewayServiceClient.cs @@ -1,7 +1,7 @@ -using FoundationaLLM.Common.Constants; -using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Interfaces; using FoundationaLLM.Common.Models.Vectorization; -using FoundationaLLM.Gateway.Interfaces; +using FoundationaLLM.Gateway.Exceptions; +using FoundationaLLM.Gateway.Models; using Microsoft.Extensions.Logging; using System.Text; using System.Text.Json; @@ -13,28 +13,24 @@ namespace FoundationaLLM.Gateway.Client /// public class GatewayServiceClient : IGatewayServiceClient { - private readonly ICallContext _callContext; - private readonly IHttpClientFactoryService _httpClientFactoryService; + private readonly HttpClient _gatewayAPIHttpClient; private readonly ILogger _logger; /// /// Creates a new instance of the Gateway API service. /// - /// Stores context information extracted from the current HTTP request. This information - /// is primarily used to inject HTTP headers into downstream HTTP calls. - /// The used to create the HTTP client. + /// An configured to call the Gateway API. /// The used for logging. public GatewayServiceClient( - ICallContext callContext, - IHttpClientFactoryService httpClientFactoryService, + HttpClient gatewayAPIHttpClient, ILogger logger) { - _callContext = callContext; - _httpClientFactoryService = httpClientFactoryService; + _gatewayAPIHttpClient = gatewayAPIHttpClient; _logger = logger; } - public async Task GetEmbeddingOperationResult(string operationId) + /// + public async Task GetEmbeddingOperationResult(string instanceId, string operationId) { var fallback = new TextEmbeddingResult { @@ -42,8 +38,7 @@ public async Task GetEmbeddingOperationResult(string operat OperationId = null }; - var client = await _httpClientFactoryService.CreateClient(HttpClientNames.GatewayAPI, _callContext.CurrentUserIdentity); - var response = await client.GetAsync($"embeddings?operationId={operationId}"); + var response = await _gatewayAPIHttpClient.GetAsync($"instances/{instanceId}/embeddings?operationId={operationId}"); if (response.IsSuccessStatusCode) { @@ -56,7 +51,8 @@ public async Task GetEmbeddingOperationResult(string operat return fallback; } - public async Task StartEmbeddingOperation(TextEmbeddingRequest embeddingRequest) + /// + public async Task StartEmbeddingOperation(string instanceId, TextEmbeddingRequest embeddingRequest) { var fallback = new TextEmbeddingResult { @@ -64,9 +60,8 @@ public async Task StartEmbeddingOperation(TextEmbeddingRequ OperationId = null }; - var client = await _httpClientFactoryService.CreateClient(HttpClientNames.GatewayAPI, _callContext.CurrentUserIdentity); var serializedRequest = JsonSerializer.Serialize(embeddingRequest); - var response = await client.PostAsync("embeddings", + var response = await _gatewayAPIHttpClient.PostAsync($"instances/{instanceId}/embeddings", new StringContent( serializedRequest, Encoding.UTF8, @@ -82,5 +77,34 @@ public async Task StartEmbeddingOperation(TextEmbeddingRequ return fallback; } + + /// + public async Task> CreateAgentCapability(string instanceId, string capabilityCategory, string capabilityName, Dictionary? parameters = null) + { + var serializedRequest = JsonSerializer.Serialize(new AgentCapabilityRequest + { + CapabilityCategory = capabilityCategory, + CapabilityName = capabilityName, + Parameters = parameters ?? [] + }); + var response = await _gatewayAPIHttpClient.PostAsync($"instances/{instanceId}/agentcapabilities", + new StringContent( + serializedRequest, + Encoding.UTF8, + "application/json")); + + if (response.IsSuccessStatusCode) + { + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObject = JsonSerializer.Deserialize>(responseContent); + + if (responseObject == null || responseObject.Count == 0) + throw new GatewayException("The Gatekeeper API returned an invalid response."); + + return responseObject; + } + + throw new GatewayException($"The Gatekeeper API returned an error status code ({response.StatusCode}) while processing the agent capability request."); + } } } diff --git a/src/dotnet/Gateway/Gateway.csproj b/src/dotnet/Gateway/Gateway.csproj index c07a308f5b..8cba8a9a79 100644 --- a/src/dotnet/Gateway/Gateway.csproj +++ b/src/dotnet/Gateway/Gateway.csproj @@ -8,6 +8,10 @@ FoundationaLLM.Gateway + + + + diff --git a/src/dotnet/Gateway/Interfaces/IGatewayCore.cs b/src/dotnet/Gateway/Interfaces/IGatewayCore.cs index 71019eeb5c..83cb204302 100644 --- a/src/dotnet/Gateway/Interfaces/IGatewayCore.cs +++ b/src/dotnet/Gateway/Interfaces/IGatewayCore.cs @@ -1,4 +1,4 @@ -using FoundationaLLM.Common.Models.Vectorization; +using FoundationaLLM.Common.Interfaces; namespace FoundationaLLM.Gateway.Interfaces { diff --git a/src/dotnet/Gateway/Interfaces/IGatewayServiceClient.cs b/src/dotnet/Gateway/Interfaces/IGatewayServiceClient.cs deleted file mode 100644 index e7e524d958..0000000000 --- a/src/dotnet/Gateway/Interfaces/IGatewayServiceClient.cs +++ /dev/null @@ -1,24 +0,0 @@ -using FoundationaLLM.Common.Models.Vectorization; - -namespace FoundationaLLM.Gateway.Interfaces -{ - /// - /// Defines the interface of the FoundationaLLM Gateway service. - /// - public interface IGatewayServiceClient - { - /// - /// Starts an embedding operation. - /// - /// The object containing the details of the embedding operation. - /// A object with the outcome of the operation. - Task StartEmbeddingOperation(TextEmbeddingRequest embeddingRequest); - - /// - /// Retrieves the outcome of an embedding operation. - /// - /// The unique identifier of the text embedding operation. - /// A object with the outcome of the operation. - Task GetEmbeddingOperationResult(string operationId); - } -} diff --git a/src/dotnet/Gateway/Models/AgentCapabilityRequest.cs b/src/dotnet/Gateway/Models/AgentCapabilityRequest.cs new file mode 100644 index 0000000000..0028d69dc7 --- /dev/null +++ b/src/dotnet/Gateway/Models/AgentCapabilityRequest.cs @@ -0,0 +1,23 @@ +namespace FoundationaLLM.Gateway.Models +{ + /// + /// Provides the details required to create an agent capability. + /// + public class AgentCapabilityRequest + { + /// + /// The category of the capability. + /// + public required string CapabilityCategory { get; set; } + + /// + /// The name of the capability to be created. + /// + public required string CapabilityName { get; set; } + + /// + /// The dictionary of parameter values used to create the capability. + /// + public required Dictionary Parameters { get; set; } = []; + } +} diff --git a/src/dotnet/Gateway/Services/DependencyInjection.cs b/src/dotnet/Gateway/Services/DependencyInjection.cs index f9b3a608e7..c5fd845d5d 100644 --- a/src/dotnet/Gateway/Services/DependencyInjection.cs +++ b/src/dotnet/Gateway/Services/DependencyInjection.cs @@ -1,4 +1,5 @@ using FoundationaLLM.Common.Constants.Configuration; +using FoundationaLLM.Common.Interfaces; using FoundationaLLM.Gateway.Client; using FoundationaLLM.Gateway.Interfaces; using FoundationaLLM.Gateway.Models.Configuration; @@ -25,12 +26,5 @@ public static void AddGatewayCore(this IHostApplicationBuilder builder) builder.Services.AddSingleton(); builder.Services.AddHostedService(); } - - /// - /// Adds the Gateway API service to the dependency injection container. - /// - /// The host application builder. - public static void AddGatewayService(this IHostApplicationBuilder builder) => - builder.Services.AddScoped(); } } diff --git a/src/dotnet/Gateway/Services/GatewayCore.cs b/src/dotnet/Gateway/Services/GatewayCore.cs index 6ee6c696c3..3a4dce445f 100644 --- a/src/dotnet/Gateway/Services/GatewayCore.cs +++ b/src/dotnet/Gateway/Services/GatewayCore.cs @@ -1,4 +1,8 @@ -using FoundationaLLM.Common.Interfaces; +using Azure.AI.OpenAI.Assistants; +using FoundationaLLM.Common.Authentication; +using FoundationaLLM.Common.Constants.Agents; +using FoundationaLLM.Common.Constants.OpenAI; +using FoundationaLLM.Common.Interfaces; using FoundationaLLM.Common.Models.Azure; using FoundationaLLM.Common.Models.Vectorization; using FoundationaLLM.Gateway.Exceptions; @@ -9,6 +13,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Concurrent; +using System.Text.Json; namespace FoundationaLLM.Gateway.Services { @@ -43,6 +48,7 @@ public async Task StartAsync(CancellationToken cancellationToken) try { var openAIAccounts = _settings.AzureOpenAIAccounts.Split(";"); + foreach (var openAIAccount in openAIAccounts) { _logger.LogInformation("Loading properties for the Azure OpenAI account with resource id {AccountResourceId}.", openAIAccount); @@ -111,7 +117,7 @@ public async Task ExecuteAsync(CancellationToken cancellationToken) } /// - public async Task StartEmbeddingOperation(TextEmbeddingRequest embeddingRequest) + public async Task StartEmbeddingOperation(string instanceId, TextEmbeddingRequest embeddingRequest) { if (!_initialized) throw new GatewayException("The Gateway service is not initialized."); @@ -152,7 +158,7 @@ public async Task StartEmbeddingOperation(TextEmbeddingRequ } /// - public async Task GetEmbeddingOperationResult(string operationId) + public async Task GetEmbeddingOperationResult(string instanceId, string operationId) { if (!_initialized) throw new GatewayException("The Gateway service is not initialized."); @@ -177,5 +183,79 @@ public async Task GetEmbeddingOperationResult(string operat else return await Task.FromResult(operationContext.Result); } + + /// + public async Task> CreateAgentCapability(string instanceId, string capabilityCategory, string capabilityName, Dictionary? parameters = null) + { + if (!_initialized) + throw new GatewayException("The Gateway service is not initialized."); + + return capabilityCategory switch + { + AgentCapabilityCategoryNames.OpenAIAssistants => await CreateOpenAIAgentCapability(instanceId, capabilityName, parameters!), + _ => throw new GatewayException($"The agent capability category {capabilityCategory} is not supported by the Gateway service.", + StatusCodes.Status400BadRequest), + }; + } + + private async Task> CreateOpenAIAgentCapability(string instanceId, string capabilityName, Dictionary parameters) + { + if (string.IsNullOrEmpty(capabilityName)) + throw new GatewayException("The specified capability name is invalid.", StatusCodes.Status400BadRequest); + + Dictionary result = []; + + var endpoint = parameters[OpenAIAgentCapabilityParameterNames.Endpoint].ToString(); + var azureOpenAIAccount = _azureOpenAIAccounts.Values.FirstOrDefault( + a => Uri.Compare( + new Uri(endpoint!), + new Uri(a.Endpoint), + UriComponents.Host, + UriFormat.SafeUnescaped, + StringComparison.OrdinalIgnoreCase) == 0) + ?? throw new GatewayException($"The Gateway service is not configured to use the {endpoint} endpoint."); + + var assistantsClient = new AssistantsClient(new Uri(azureOpenAIAccount.Endpoint), DefaultAuthentication.AzureCredential); + + parameters.TryGetValue(OpenAIAgentCapabilityParameterNames.CreateAssistant, out var createAssistantObject); + var createAssistant = ((JsonElement)createAssistantObject!).Deserialize(); + + parameters.TryGetValue(OpenAIAgentCapabilityParameterNames.CreateAssistantThread, out var createAssistantThreadObject); + var createAssistantThread = ((JsonElement)createAssistantThreadObject!).Deserialize(); + + if (createAssistant) + { + var prompt = parameters[OpenAIAgentCapabilityParameterNames.AssistantPrompt].ToString(); + var modelDeploymentName = parameters[OpenAIAgentCapabilityParameterNames.ModelDeploymentName].ToString(); + var azureOpenAIModel = azureOpenAIAccount.Deployments.FirstOrDefault( + d => string.Compare( + modelDeploymentName, + d.Name, + true) == 0) + ?? throw new GatewayException($"The Gateway service cannot find the {modelDeploymentName} model deployment in the account with endpoint {endpoint}."); + + var response = await assistantsClient.CreateAssistantAsync(new AssistantCreationOptions(modelDeploymentName) + { + Name = capabilityName, + Instructions = prompt, + Tools = + { + new CodeInterpreterToolDefinition() + } + }); + + var assistant = response.Value; + result[OpenAIAgentCapabilityParameterNames.AssistantId] = assistant.Id; + } + + if (createAssistantThread) + { + var response = await assistantsClient.CreateThreadAsync(); + var thread = response.Value; + result[OpenAIAgentCapabilityParameterNames.AssistantThreadId] = thread.Id; + } + + return result; + } } } diff --git a/src/dotnet/GatewayAPI/Controllers/AgentCapabilitiesController.cs b/src/dotnet/GatewayAPI/Controllers/AgentCapabilitiesController.cs new file mode 100644 index 0000000000..debfdf3ba6 --- /dev/null +++ b/src/dotnet/GatewayAPI/Controllers/AgentCapabilitiesController.cs @@ -0,0 +1,45 @@ +using FoundationaLLM.Common.Authentication; +using FoundationaLLM.Common.Constants.OpenAI; +using FoundationaLLM.Gateway.Interfaces; +using FoundationaLLM.Gateway.Models; +using Microsoft.AspNetCore.Mvc; + +namespace FoundationaLLM.Gateway.API.Controllers +{ + /// + /// Methods for managing agent capabilities. + /// + /// The that provides LLM gateway services. + [ApiController] + [APIKeyAuthentication] + [Route("instances/{instanceId}/[controller]")] + public class AgentCapabilitiesController( + IGatewayCore gatewayCore) + { + readonly IGatewayCore _gatewayCore = gatewayCore; + + /// + /// Creates an agent capability. + /// + /// The FoundationaLLM instance id. + /// The object with the deails of the requested capability. + /// A dictionary of output values. + /// + /// The supported categories are: + /// + /// + /// OpenAI.Assistants (the names of the keys for the output dictionary are defined in ) + /// + /// + /// + [HttpPost] + public async Task CreateAgentCapability( + string instanceId, + [FromBody] AgentCapabilityRequest agentCapabilityRequest) => + new OkObjectResult(await _gatewayCore.CreateAgentCapability( + instanceId, + agentCapabilityRequest.CapabilityCategory, + agentCapabilityRequest.CapabilityName, + agentCapabilityRequest.Parameters)); + } +} diff --git a/src/dotnet/GatewayAPI/Controllers/EmbeddingsController.cs b/src/dotnet/GatewayAPI/Controllers/EmbeddingsController.cs index e9085cfef8..3a659d173d 100644 --- a/src/dotnet/GatewayAPI/Controllers/EmbeddingsController.cs +++ b/src/dotnet/GatewayAPI/Controllers/EmbeddingsController.cs @@ -11,7 +11,7 @@ namespace FoundationaLLM.Gateway.API.Controllers /// The that provides LLM gateway services. [ApiController] [APIKeyAuthentication] - [Route("[controller]")] + [Route("instances/{instanceId}/[controller]")] public class EmbeddingsController( IGatewayCore gatewayCore) { @@ -20,20 +20,25 @@ public class EmbeddingsController( /// /// Handles an incoming text embedding request by starting a new embedding operation. /// + /// The FoundationaLLM instance id. /// The object with the details of the embedding request. /// A object with the outcome of the operation. [HttpPost] public async Task StartEmbeddingOperation( + string instanceId, [FromBody] TextEmbeddingRequest embeddingRequest) => - new OkObjectResult(await _gatewayCore.StartEmbeddingOperation(embeddingRequest)); + new OkObjectResult(await _gatewayCore.StartEmbeddingOperation(instanceId, embeddingRequest)); /// /// Retrieves the outcome of a text embedding operation. /// + /// The FoundationaLLM instance id. /// The unique identifier of the text embedding operation. /// A object with the outcome of the operation. [HttpGet] - public async Task GetEmbeddingOperationResult(string operationId) => - new OkObjectResult(await _gatewayCore.GetEmbeddingOperationResult(operationId)); + public async Task GetEmbeddingOperationResult( + string instanceId, + string operationId) => + new OkObjectResult(await _gatewayCore.GetEmbeddingOperationResult(instanceId, operationId)); } } diff --git a/src/dotnet/GatewayAPI/Program.cs b/src/dotnet/GatewayAPI/Program.cs index adf2b6c895..fd9a989c1b 100644 --- a/src/dotnet/GatewayAPI/Program.cs +++ b/src/dotnet/GatewayAPI/Program.cs @@ -4,6 +4,7 @@ using FoundationaLLM.Common.Constants; using FoundationaLLM.Common.Constants.Configuration; using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Models.Configuration.Instance; using FoundationaLLM.Common.OpenAPI; using FoundationaLLM.Common.Services.Azure; using Microsoft.Extensions.Options; @@ -25,6 +26,7 @@ { options.SetCredential(DefaultAuthentication.AzureCredential); }); + options.Select(AppConfigurationKeyFilters.FoundationaLLM_Instance); options.Select(AppConfigurationKeyFilters.FoundationaLLM_APIEndpoints_GatewayAPI_Essentials); options.Select(AppConfigurationKeyFilters.FoundationaLLM_APIEndpoints_GatewayAPI_Configuration); }); @@ -47,6 +49,9 @@ // Core Gateway service builder.AddGatewayCore(); +builder.Services.AddOptions() + .Bind(builder.Configuration.GetSection(AppConfigurationKeySections.FoundationaLLM_Instance)); + // Open API (Swagger) builder.Services.AddTransient, ConfigureSwaggerOptions>(); diff --git a/src/dotnet/ManagementClient/Clients/Resources/AgentManagementClient.cs b/src/dotnet/ManagementClient/Clients/Resources/AgentManagementClient.cs index 90b43443e5..b6db0e9305 100644 --- a/src/dotnet/ManagementClient/Clients/Resources/AgentManagementClient.cs +++ b/src/dotnet/ManagementClient/Clients/Resources/AgentManagementClient.cs @@ -43,7 +43,7 @@ public async Task CheckAgentNameAsync(ResourceName reso return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Agent, - $"{AgentResourceTypeNames.Agents}/{AgentResourceProviderActions.CheckName}", + $"{AgentResourceTypeNames.Agents}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -58,7 +58,7 @@ public async Task PurgeAgentAsync(string agentName return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Agent, - $"{AgentResourceTypeNames.Agents}/{agentName}/{AgentResourceProviderActions.Purge}", + $"{AgentResourceTypeNames.Agents}/{agentName}/{ResourceProviderActions.Purge}", new { } ); } diff --git a/src/dotnet/ManagementClient/Clients/Resources/DataSourceManagementClient.cs b/src/dotnet/ManagementClient/Clients/Resources/DataSourceManagementClient.cs index 093db6dcd7..8434115268 100644 --- a/src/dotnet/ManagementClient/Clients/Resources/DataSourceManagementClient.cs +++ b/src/dotnet/ManagementClient/Clients/Resources/DataSourceManagementClient.cs @@ -42,7 +42,7 @@ public async Task CheckDataSourceNameAsync(ResourceName return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{DataSourceResourceProviderActions.CheckName}", + $"{DataSourceResourceTypeNames.DataSources}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -57,7 +57,7 @@ public async Task PurgeDataSourceAsync(string data return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{dataSourceName}/{DataSourceResourceProviderActions.Purge}", + $"{DataSourceResourceTypeNames.DataSources}/{dataSourceName}/{ResourceProviderActions.Purge}", new { } ); } @@ -66,7 +66,7 @@ public async Task PurgeDataSourceAsync(string data public async Task> FilterDataSourceAsync(ResourceFilter resourceFilter) => await managementRestClient.Resources.ExecuteResourceActionAsync>( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{DataSourceResourceProviderActions.Filter}", + $"{DataSourceResourceTypeNames.DataSources}/{ResourceProviderActions.Filter}", resourceFilter ); diff --git a/src/dotnet/ManagementClient/Clients/Resources/PromptManagementClient.cs b/src/dotnet/ManagementClient/Clients/Resources/PromptManagementClient.cs index 95ad218eae..89700b699e 100644 --- a/src/dotnet/ManagementClient/Clients/Resources/PromptManagementClient.cs +++ b/src/dotnet/ManagementClient/Clients/Resources/PromptManagementClient.cs @@ -42,7 +42,7 @@ public async Task CheckPromptNameAsync(ResourceName res return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Prompt, - $"{PromptResourceTypeNames.Prompts}/{PromptResourceProviderActions.CheckName}", + $"{PromptResourceTypeNames.Prompts}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -57,7 +57,7 @@ public async Task PurgePromptAsync(string promptNa return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Prompt, - $"{PromptResourceTypeNames.Prompts}/{promptName}/{PromptResourceProviderActions.Purge}", + $"{PromptResourceTypeNames.Prompts}/{promptName}/{ResourceProviderActions.Purge}", new { } ); } diff --git a/src/dotnet/ManagementClient/Clients/Resources/VectorizationManagementClient.cs b/src/dotnet/ManagementClient/Clients/Resources/VectorizationManagementClient.cs index cd8612b235..6c2c1744f3 100644 --- a/src/dotnet/ManagementClient/Clients/Resources/VectorizationManagementClient.cs +++ b/src/dotnet/ManagementClient/Clients/Resources/VectorizationManagementClient.cs @@ -151,7 +151,7 @@ public async Task PurgeVectorizationPipelineAsync( return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.VectorizationPipelines}/{pipelineName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.VectorizationPipelines}/{pipelineName}/{ResourceProviderActions.Purge}", new { } ); } @@ -181,7 +181,7 @@ public async Task PurgeTextPartitioningProfileAsyn return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.TextPartitioningProfiles}/{textPartitioningProfileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.TextPartitioningProfiles}/{textPartitioningProfileName}/{ResourceProviderActions.Purge}", new { } ); } @@ -211,7 +211,7 @@ public async Task PurgeTextEmbeddingProfileAsync(s return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.TextEmbeddingProfiles}/{textEmbeddingProfileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.TextEmbeddingProfiles}/{textEmbeddingProfileName}/{ResourceProviderActions.Purge}", new { } ); } @@ -226,7 +226,7 @@ public async Task CheckIndexingProfileNameAsync(Resourc return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{VectorizationResourceProviderActions.CheckName}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -235,7 +235,7 @@ public async Task CheckIndexingProfileNameAsync(Resourc public async Task> FilterIndexingProfileAsync(ResourceFilter resourceFilter) => await managementRestClient.Resources.ExecuteResourceActionAsync>( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{VectorizationResourceProviderActions.Filter}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{ResourceProviderActions.Filter}", resourceFilter ); @@ -249,7 +249,7 @@ public async Task PurgeIndexingProfileAsync(string return await managementRestClient.Resources.ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{indexingProfileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{indexingProfileName}/{ResourceProviderActions.Purge}", new { } ); } diff --git a/src/dotnet/Orchestration/Orchestration.csproj b/src/dotnet/Orchestration/Orchestration.csproj index 67f4a93884..10e4427f3d 100644 --- a/src/dotnet/Orchestration/Orchestration.csproj +++ b/src/dotnet/Orchestration/Orchestration.csproj @@ -24,6 +24,7 @@ + diff --git a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs index 15383fb694..fc1ff8a580 100644 --- a/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs +++ b/src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs @@ -1,4 +1,4 @@ -using FoundationaLLM.Common.Constants; +using FoundationaLLM.Common.Constants; using FoundationaLLM.Common.Constants.Agents; using FoundationaLLM.Common.Constants.ResourceProviders; using FoundationaLLM.Common.Exceptions; @@ -8,6 +8,7 @@ using FoundationaLLM.Common.Models.Orchestration; using FoundationaLLM.Common.Models.ResourceProviders.Agent; using FoundationaLLM.Common.Models.ResourceProviders.AIModel; +using FoundationaLLM.Common.Models.ResourceProviders.AzureOpenAI; using FoundationaLLM.Common.Models.ResourceProviders.Configuration; using FoundationaLLM.Common.Models.ResourceProviders.DataSource; using FoundationaLLM.Common.Models.ResourceProviders.Prompt; @@ -57,14 +58,25 @@ public class OrchestrationBuilder var logger = loggerFactory.CreateLogger(); var result = await LoadAgent( + instanceId, agentName, + originalRequest.SessionId, originalRequest.Settings?.ModelParameters, resourceProviderServices, callContext.CurrentUserIdentity!, logger); if (result.Agent == null) return null; - + + await EnsureAgentCapabilities( + instanceId, + result.Agent, + originalRequest.SessionId!, + result.ExplodedObjects!, + resourceProviderServices, + callContext.CurrentUserIdentity!, + logger); + if (result.Agent.AgentType == typeof(KnowledgeManagementAgent) || result.Agent.AgentType == typeof(AudioClassificationAgent)) { var orchestrationName = string.IsNullOrWhiteSpace(result.Agent.OrchestrationSettings?.Orchestrator) @@ -72,7 +84,7 @@ public class OrchestrationBuilder : result.Agent.OrchestrationSettings?.Orchestrator; var orchestrationService = llmOrchestrationServiceManager.GetService(instanceId, orchestrationName!, serviceProvider, callContext); - + var kmOrchestration = new KnowledgeManagementOrchestration( (KnowledgeManagementAgent)result.Agent, result.ExplodedObjects ?? [], @@ -89,7 +101,9 @@ public class OrchestrationBuilder } private static async Task<(AgentBase? Agent, Dictionary? ExplodedObjects, bool DataSourceAccessDenied)> LoadAgent( - string? agentName, + string instanceId, + string agentName, + string? sessionId, Dictionary? modelParameterOverrides, Dictionary resourceProviderServices, UnifiedUserIdentity currentUserIdentity, @@ -152,12 +166,13 @@ public class OrchestrationBuilder .ToDictionary(x => x.Name, x => x.Description); explodedObjects[CompletionRequestObjectsKeys.AllAgents] = allAgentsDescriptions; + #region Knowledge management processing if (agentBase.AgentType == typeof(KnowledgeManagementAgent) || agentBase.AgentType == typeof(AudioClassificationAgent)) { KnowledgeManagementAgent kmAgent = (KnowledgeManagementAgent)agentBase; // Check for inline-context agents, they are valid KM agents that do not have a vectorization section. - if (kmAgent is {Vectorization: not null, InlineContext: false}) + if (kmAgent is { Vectorization: not null, InlineContext: false }) { if (!string.IsNullOrWhiteSpace(kmAgent.Vectorization.DataSourceObjectId)) { @@ -202,7 +217,98 @@ public class OrchestrationBuilder } } + #endregion + return (agentBase, explodedObjects, false); } + + private static async Task EnsureAgentCapabilities( + string instanceId, + AgentBase agent, + string sessionId, + Dictionary explodedObjects, + Dictionary resourceProviderServices, + UnifiedUserIdentity currentUserIdentity, + ILogger logger) + { + if (!resourceProviderServices.TryGetValue(ResourceProviderNames.FoundationaLLM_AzureOpenAI, out var azureOpenAIResourceProvider)) + throw new OrchestrationException($"The resource provider {ResourceProviderNames.FoundationaLLM_AzureOpenAI} was not loaded."); + + var prompt = explodedObjects[agent.PromptObjectId!] as MultipartPrompt; + var aiModel = explodedObjects[agent.AIModelObjectId!] as AIModelBase; + var apiEndpointConfiguration = explodedObjects[aiModel!.EndpointObjectId!] as APIEndpointConfiguration; + + if (agent.HasCapability(AgentCapabilityCategoryNames.OpenAIAssistants)) + { + var assistantUserContextName = $"{currentUserIdentity.UPN?.NormalizeUserPrincipalName() ?? currentUserIdentity.UserId}-{instanceId.ToLower()}"; + + if (!await azureOpenAIResourceProvider.ResourceExists( + instanceId, + assistantUserContextName, + AzureOpenAIResourceTypeNames.AssistantUserContext, + currentUserIdentity)) + { + var result = await azureOpenAIResourceProvider.CreateOrUpdateResource( + instanceId, + new AssistantUserContext + { + Name = assistantUserContextName, + UserPrincipalName = currentUserIdentity.UPN ?? currentUserIdentity.UserId!, + Endpoint = apiEndpointConfiguration!.Url, + ModelDeploymentName = aiModel.DeploymentName!, + Prompt = prompt!.Prefix!, + Conversations = new() + { + { + sessionId!, + new AssistantConversation + { + SessionId = sessionId! + } + } + } + }, + AzureOpenAIResourceTypeNames.AssistantUserContext, + currentUserIdentity); + + if (!string.IsNullOrWhiteSpace(result.NewOpenAIAssistantId)) + explodedObjects[CompletionRequestObjectsKeys.OpenAIAssistantId] = result.NewOpenAIAssistantId; + + if (!string.IsNullOrWhiteSpace(result.NewOpenAIAssistantThreadId)) + explodedObjects[CompletionRequestObjectsKeys.OpenAIAssistantThreadId] = result.NewOpenAIAssistantThreadId; + } + else + { + var assistantUserContext = await azureOpenAIResourceProvider.GetResource( + instanceId, + assistantUserContextName, + AzureOpenAIResourceTypeNames.AssistantUserContext, + currentUserIdentity); + + explodedObjects[CompletionRequestObjectsKeys.OpenAIAssistantId] = assistantUserContext.OpenAIAssistantId!; + + if (!assistantUserContext.Conversations.TryGetValue(sessionId!, out AssistantConversation? assistantConversation)) + { + assistantUserContext.Conversations.Add( + sessionId!, + new AssistantConversation + { + SessionId = sessionId! + }); + + var result = await azureOpenAIResourceProvider.CreateOrUpdateResource( + instanceId, + assistantUserContext, + AzureOpenAIResourceTypeNames.AssistantUserContext, + currentUserIdentity); + + if (!string.IsNullOrWhiteSpace(result.NewOpenAIAssistantThreadId)) + explodedObjects[CompletionRequestObjectsKeys.OpenAIAssistantThreadId] = result.NewOpenAIAssistantThreadId; + } + + explodedObjects[CompletionRequestObjectsKeys.OpenAIAssistantThreadId] = assistantConversation!.OpenAIThreadId!; + } + } + } } } diff --git a/src/dotnet/Orchestration/Services/OrchestrationService.cs b/src/dotnet/Orchestration/Services/OrchestrationService.cs index 37edf333ba..a6fc0fb997 100644 --- a/src/dotnet/Orchestration/Services/OrchestrationService.cs +++ b/src/dotnet/Orchestration/Services/OrchestrationService.cs @@ -2,6 +2,7 @@ using FoundationaLLM.Common.Constants.Configuration; using FoundationaLLM.Common.Constants.ResourceProviders; using FoundationaLLM.Common.Exceptions; +using FoundationaLLM.Common.Extensions; using FoundationaLLM.Common.Interfaces; using FoundationaLLM.Common.Models.Infrastructure; using FoundationaLLM.Common.Models.Orchestration; @@ -218,11 +219,12 @@ private async Task ValidAgentName(string instanceId, string agentName) { var agentResourceProvider = _resourceProviderServices[ResourceProviderNames.FoundationaLLM_Agent]; - var result = await agentResourceProvider.HandlePostAsync( - $"/instances/{instanceId}/{AgentResourceTypeNames.Agents}/{AgentResourceProviderActions.CheckName}", - JsonSerializer.Serialize(new ResourceName { Name = agentName }), + var result = await agentResourceProvider.CheckResourceName( + instanceId, + agentName, + AgentResourceTypeNames.Agents, _callContext.CurrentUserIdentity!); - return true; + return result.Status == NameCheckResultType.Allowed; } } diff --git a/src/dotnet/OrchestrationAPI/OrchestrationAPI.csproj b/src/dotnet/OrchestrationAPI/OrchestrationAPI.csproj index adb2d43ad9..ce3e6c24e4 100644 --- a/src/dotnet/OrchestrationAPI/OrchestrationAPI.csproj +++ b/src/dotnet/OrchestrationAPI/OrchestrationAPI.csproj @@ -44,6 +44,7 @@ + diff --git a/src/dotnet/OrchestrationAPI/Program.cs b/src/dotnet/OrchestrationAPI/Program.cs index 29f599f20e..804b86a1de 100644 --- a/src/dotnet/OrchestrationAPI/Program.cs +++ b/src/dotnet/OrchestrationAPI/Program.cs @@ -60,6 +60,7 @@ public static void Main(string[] args) options.Select(AppConfigurationKeyFilters.FoundationaLLM_ResourceProviders_Attachment_Storage); options.Select(AppConfigurationKeyFilters.FoundationaLLM_ResourceProviders_AIModel_Storage); options.Select(AppConfigurationKeyFilters.FoundationaLLM_ResourceProviders_Prompt_Storage); + options.Select(AppConfigurationKeyFilters.FoundationaLLM_ResourceProviders_AzureOpenAI_Storage); options.Select(AppConfigurationKeyFilters.FoundationaLLM_Events_Profiles_OrchestrationAPI); })); @@ -125,6 +126,7 @@ public static void Main(string[] args) builder.AddDataSourceResourceProvider(); builder.AddAttachmentResourceProvider(); builder.AddAIModelResourceProvider(); + builder.AddAzureOpenAIResourceProvider(); // Register the downstream services and HTTP clients. builder.AddHttpClientFactoryService(); diff --git a/src/dotnet/Prompt/ResourceProviders/PromptResourceProviderService.cs b/src/dotnet/Prompt/ResourceProviders/PromptResourceProviderService.cs index f7e66401af..2ddb6c5bff 100644 --- a/src/dotnet/Prompt/ResourceProviders/PromptResourceProviderService.cs +++ b/src/dotnet/Prompt/ResourceProviders/PromptResourceProviderService.cs @@ -254,8 +254,8 @@ protected override async Task ExecuteActionAsync(ResourcePath resourcePa { PromptResourceTypeNames.Prompts => resourcePath.ResourceTypeInstances.Last().Action switch { - PromptResourceProviderActions.CheckName => CheckPromptName(serializedAction), - PromptResourceProviderActions.Purge => await PurgeResource(resourcePath), + ResourceProviderActions.CheckName => CheckPromptName(serializedAction), + ResourceProviderActions.Purge => await PurgeResource(resourcePath), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, diff --git a/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs b/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs index 1c5d5d1694..17ea168fc5 100644 --- a/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs +++ b/src/dotnet/Vectorization/ResourceProviders/VectorizationResourceProviderService.cs @@ -314,9 +314,9 @@ protected override async Task ExecuteActionAsync(ResourcePath resourcePa { VectorizationResourceTypeNames.IndexingProfiles => resourcePath.ResourceTypeInstances.Last().Action switch { - VectorizationResourceProviderActions.CheckName => CheckProfileName(serializedAction, _indexingProfiles), - VectorizationResourceProviderActions.Filter => Filter(serializedAction, _indexingProfiles, _defaultIndexingProfileName), - VectorizationResourceProviderActions.Purge => await PurgeResource(resourcePath, _indexingProfiles, INDEXING_PROFILES_FILE_PATH), + ResourceProviderActions.CheckName => CheckProfileName(serializedAction, _indexingProfiles), + ResourceProviderActions.Filter => Filter(serializedAction, _indexingProfiles, _defaultIndexingProfileName), + ResourceProviderActions.Purge => await PurgeResource(resourcePath, _indexingProfiles, INDEXING_PROFILES_FILE_PATH), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, @@ -324,19 +324,19 @@ protected override async Task ExecuteActionAsync(ResourcePath resourcePa { VectorizationResourceProviderActions.Activate => await SetPipelineActivation(resourcePath.ResourceTypeInstances.Last().ResourceId!, true), VectorizationResourceProviderActions.Deactivate => await SetPipelineActivation(resourcePath.ResourceTypeInstances.Last().ResourceId!, false), - VectorizationResourceProviderActions.Purge => await PurgeResource(resourcePath, _pipelines, PIPELINES_FILE_PATH), + ResourceProviderActions.Purge => await PurgeResource(resourcePath, _pipelines, PIPELINES_FILE_PATH), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, VectorizationResourceTypeNames.TextPartitioningProfiles => resourcePath.ResourceTypeInstances.Last().Action switch { - VectorizationResourceProviderActions.Purge => await PurgeResource(resourcePath, _textPartitioningProfiles, TEXT_PARTITIONING_PROFILES_FILE_PATH), + ResourceProviderActions.Purge => await PurgeResource(resourcePath, _textPartitioningProfiles, TEXT_PARTITIONING_PROFILES_FILE_PATH), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, VectorizationResourceTypeNames.TextEmbeddingProfiles => resourcePath.ResourceTypeInstances.Last().Action switch { - VectorizationResourceProviderActions.Purge => await PurgeResource(resourcePath, _textEmbeddingProfiles, TEXT_EMBEDDING_PROFILES_FILE_PATH), + ResourceProviderActions.Purge => await PurgeResource(resourcePath, _textEmbeddingProfiles, TEXT_EMBEDDING_PROFILES_FILE_PATH), _ => throw new ResourceProviderException($"The action {resourcePath.ResourceTypeInstances.Last().Action} is not supported by the {_name} resource provider.", StatusCodes.Status400BadRequest) }, diff --git a/src/dotnet/Vectorization/Services/Text/GatewayTextEmbeddingService.cs b/src/dotnet/Vectorization/Services/Text/GatewayTextEmbeddingService.cs index 5d47ab4784..395bdfb14b 100644 --- a/src/dotnet/Vectorization/Services/Text/GatewayTextEmbeddingService.cs +++ b/src/dotnet/Vectorization/Services/Text/GatewayTextEmbeddingService.cs @@ -1,21 +1,25 @@ using FoundationaLLM.Common.Interfaces; +using FoundationaLLM.Common.Models.Configuration.Instance; using FoundationaLLM.Common.Models.Vectorization; -using FoundationaLLM.Gateway.Interfaces; +using Microsoft.Extensions.Options; namespace FoundationaLLM.Vectorization.Services.Text { /// /// Generates text embeddings by routing requests through the FoundationaLLM Gateway API. /// + /// The options providing the with instance settings. /// The used to call the Gateway API. public class GatewayTextEmbeddingService( + IOptions instanceOptions, IGatewayServiceClient gatewayService) : ITextEmbeddingService { + private readonly InstanceSettings _instanceSettings = instanceOptions.Value; private readonly IGatewayServiceClient _gatewayService = gatewayService; /// public async Task GetEmbeddingsAsync(IList textChunks, string modelName) => - await _gatewayService.StartEmbeddingOperation(new TextEmbeddingRequest + await _gatewayService.StartEmbeddingOperation(_instanceSettings.Id, new TextEmbeddingRequest { EmbeddingModelName = modelName, TextChunks = textChunks @@ -23,6 +27,6 @@ await _gatewayService.StartEmbeddingOperation(new TextEmbeddingRequest /// public async Task GetEmbeddingsAsync(string operationId) => - await _gatewayService.GetEmbeddingOperationResult(operationId); + await _gatewayService.GetEmbeddingOperationResult(_instanceSettings.Id, operationId); } } diff --git a/src/dotnet/VectorizationAPI/Program.cs b/src/dotnet/VectorizationAPI/Program.cs index aabcc88429..c3f22a9d24 100644 --- a/src/dotnet/VectorizationAPI/Program.cs +++ b/src/dotnet/VectorizationAPI/Program.cs @@ -153,7 +153,6 @@ // Gateway text embedding builder.Services.AddKeyedScoped( DependencyInjectionKeys.FoundationaLLM_Vectorization_TextEmbedding_Gateway); -builder.AddGatewayService(); builder.Services.AddScoped(); builder.AddHttpClientFactoryService(); diff --git a/src/dotnet/VectorizationWorker/Program.cs b/src/dotnet/VectorizationWorker/Program.cs index 9b5dcb4641..e268a6468f 100644 --- a/src/dotnet/VectorizationWorker/Program.cs +++ b/src/dotnet/VectorizationWorker/Program.cs @@ -155,7 +155,6 @@ // Gateway text embedding builder.Services.AddKeyedScoped( DependencyInjectionKeys.FoundationaLLM_Vectorization_TextEmbedding_Gateway); -builder.AddGatewayService(); builder.Services.AddScoped(); builder.AddHttpClientFactoryService(); diff --git a/tests/dotnet/Management.Client.Tests/Clients/Resources/AgentManagementClientTests.cs b/tests/dotnet/Management.Client.Tests/Clients/Resources/AgentManagementClientTests.cs index 689744a665..7c944b2c41 100644 --- a/tests/dotnet/Management.Client.Tests/Clients/Resources/AgentManagementClientTests.cs +++ b/tests/dotnet/Management.Client.Tests/Clients/Resources/AgentManagementClientTests.cs @@ -122,7 +122,7 @@ public async Task CheckAgentNameAsync_ShouldReturnCheckResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Agent, - $"{AgentResourceTypeNames.Agents}/{AgentResourceProviderActions.CheckName}", + $"{AgentResourceTypeNames.Agents}/{ResourceProviderActions.CheckName}", resourceName ) .Returns(Task.FromResult(expectedCheckResult)); @@ -134,7 +134,7 @@ public async Task CheckAgentNameAsync_ShouldReturnCheckResult() Assert.Equal(expectedCheckResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Agent, - $"{AgentResourceTypeNames.Agents}/{AgentResourceProviderActions.CheckName}", + $"{AgentResourceTypeNames.Agents}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -159,7 +159,7 @@ public async Task PurgeAgentAsync_ShouldReturnPurgeResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Agent, - $"{AgentResourceTypeNames.Agents}/{agentName}/{AgentResourceProviderActions.Purge}", + $"{AgentResourceTypeNames.Agents}/{agentName}/{ResourceProviderActions.Purge}", Arg.Any() ) .Returns(Task.FromResult(expectedPurgeResult)); @@ -171,7 +171,7 @@ public async Task PurgeAgentAsync_ShouldReturnPurgeResult() Assert.Equal(expectedPurgeResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Agent, - $"{AgentResourceTypeNames.Agents}/{agentName}/{AgentResourceProviderActions.Purge}", + $"{AgentResourceTypeNames.Agents}/{agentName}/{ResourceProviderActions.Purge}", Arg.Any() ); } diff --git a/tests/dotnet/Management.Client.Tests/Clients/Resources/DataSourceManagementClientTests.cs b/tests/dotnet/Management.Client.Tests/Clients/Resources/DataSourceManagementClientTests.cs index d1bff64d6e..49ab8ed3e3 100644 --- a/tests/dotnet/Management.Client.Tests/Clients/Resources/DataSourceManagementClientTests.cs +++ b/tests/dotnet/Management.Client.Tests/Clients/Resources/DataSourceManagementClientTests.cs @@ -149,7 +149,7 @@ public async Task CheckDataSourceNameAsync_ShouldReturnCheckResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{DataSourceResourceProviderActions.CheckName}", + $"{DataSourceResourceTypeNames.DataSources}/{ResourceProviderActions.CheckName}", resourceName ) .Returns(Task.FromResult(expectedCheckResult)); @@ -161,7 +161,7 @@ public async Task CheckDataSourceNameAsync_ShouldReturnCheckResult() Assert.Equal(expectedCheckResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{DataSourceResourceProviderActions.CheckName}", + $"{DataSourceResourceTypeNames.DataSources}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -186,7 +186,7 @@ public async Task PurgeDataSourceAsync_ShouldReturnPurgeResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{dataSourceName}/{DataSourceResourceProviderActions.Purge}", + $"{DataSourceResourceTypeNames.DataSources}/{dataSourceName}/{ResourceProviderActions.Purge}", Arg.Any() ) .Returns(Task.FromResult(expectedPurgeResult)); @@ -198,7 +198,7 @@ public async Task PurgeDataSourceAsync_ShouldReturnPurgeResult() Assert.Equal(expectedPurgeResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{dataSourceName}/{DataSourceResourceProviderActions.Purge}", + $"{DataSourceResourceTypeNames.DataSources}/{dataSourceName}/{ResourceProviderActions.Purge}", Arg.Any() ); } @@ -233,7 +233,7 @@ public async Task FilterDataSourceAsync_ShouldReturnFilteredDataSources() _mockRestClient.Resources .ExecuteResourceActionAsync>( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{DataSourceResourceProviderActions.Filter}", + $"{DataSourceResourceTypeNames.DataSources}/{ResourceProviderActions.Filter}", resourceFilter ) .Returns(Task.FromResult(expectedDataSources)); @@ -245,7 +245,7 @@ public async Task FilterDataSourceAsync_ShouldReturnFilteredDataSources() Assert.Equal(expectedDataSources, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync>( ResourceProviderNames.FoundationaLLM_DataSource, - $"{DataSourceResourceTypeNames.DataSources}/{DataSourceResourceProviderActions.Filter}", + $"{DataSourceResourceTypeNames.DataSources}/{ResourceProviderActions.Filter}", resourceFilter ); } diff --git a/tests/dotnet/Management.Client.Tests/Clients/Resources/PromptManagementClientTests.cs b/tests/dotnet/Management.Client.Tests/Clients/Resources/PromptManagementClientTests.cs index 8729df0f7d..6bfe966816 100644 --- a/tests/dotnet/Management.Client.Tests/Clients/Resources/PromptManagementClientTests.cs +++ b/tests/dotnet/Management.Client.Tests/Clients/Resources/PromptManagementClientTests.cs @@ -131,7 +131,7 @@ public async Task CheckPromptNameAsync_ShouldReturnCheckResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Prompt, - $"{PromptResourceTypeNames.Prompts}/{PromptResourceProviderActions.CheckName}", + $"{PromptResourceTypeNames.Prompts}/{ResourceProviderActions.CheckName}", resourceName ) .Returns(Task.FromResult(expectedCheckResult)); @@ -143,7 +143,7 @@ public async Task CheckPromptNameAsync_ShouldReturnCheckResult() Assert.Equal(expectedCheckResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Prompt, - $"{PromptResourceTypeNames.Prompts}/{PromptResourceProviderActions.CheckName}", + $"{PromptResourceTypeNames.Prompts}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -168,7 +168,7 @@ public async Task PurgePromptAsync_ShouldReturnPurgeResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Prompt, - $"{PromptResourceTypeNames.Prompts}/{promptName}/{PromptResourceProviderActions.Purge}", + $"{PromptResourceTypeNames.Prompts}/{promptName}/{ResourceProviderActions.Purge}", Arg.Any() ) .Returns(Task.FromResult(expectedPurgeResult)); @@ -180,7 +180,7 @@ public async Task PurgePromptAsync_ShouldReturnPurgeResult() Assert.Equal(expectedPurgeResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Prompt, - $"{PromptResourceTypeNames.Prompts}/{promptName}/{PromptResourceProviderActions.Purge}", + $"{PromptResourceTypeNames.Prompts}/{promptName}/{ResourceProviderActions.Purge}", Arg.Any() ); } diff --git a/tests/dotnet/Management.Client.Tests/Clients/Resources/VectorizationManagementClientTests.cs b/tests/dotnet/Management.Client.Tests/Clients/Resources/VectorizationManagementClientTests.cs index 17b5b87d73..b31bbf9c52 100644 --- a/tests/dotnet/Management.Client.Tests/Clients/Resources/VectorizationManagementClientTests.cs +++ b/tests/dotnet/Management.Client.Tests/Clients/Resources/VectorizationManagementClientTests.cs @@ -532,7 +532,7 @@ public async Task PurgeVectorizationPipelineAsync_ShouldReturnPurgeResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.VectorizationPipelines}/{pipelineName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.VectorizationPipelines}/{pipelineName}/{ResourceProviderActions.Purge}", Arg.Any() ) .Returns(Task.FromResult(expectedPurgeResult)); @@ -544,7 +544,7 @@ public async Task PurgeVectorizationPipelineAsync_ShouldReturnPurgeResult() Assert.Equal(expectedPurgeResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.VectorizationPipelines}/{pipelineName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.VectorizationPipelines}/{pipelineName}/{ResourceProviderActions.Purge}", Arg.Any() ); } @@ -569,7 +569,7 @@ public async Task PurgeTextPartitioningProfileAsync_ShouldReturnPurgeResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.TextPartitioningProfiles}/{profileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.TextPartitioningProfiles}/{profileName}/{ResourceProviderActions.Purge}", Arg.Any() ) .Returns(Task.FromResult(expectedPurgeResult)); @@ -581,7 +581,7 @@ public async Task PurgeTextPartitioningProfileAsync_ShouldReturnPurgeResult() Assert.Equal(expectedPurgeResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.TextPartitioningProfiles}/{profileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.TextPartitioningProfiles}/{profileName}/{ResourceProviderActions.Purge}", Arg.Any() ); } @@ -606,7 +606,7 @@ public async Task PurgeTextEmbeddingProfileAsync_ShouldReturnPurgeResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.TextEmbeddingProfiles}/{profileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.TextEmbeddingProfiles}/{profileName}/{ResourceProviderActions.Purge}", Arg.Any() ) .Returns(Task.FromResult(expectedPurgeResult)); @@ -618,7 +618,7 @@ public async Task PurgeTextEmbeddingProfileAsync_ShouldReturnPurgeResult() Assert.Equal(expectedPurgeResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.TextEmbeddingProfiles}/{profileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.TextEmbeddingProfiles}/{profileName}/{ResourceProviderActions.Purge}", Arg.Any() ); } @@ -648,7 +648,7 @@ public async Task CheckIndexingProfileNameAsync_ShouldReturnCheckResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{VectorizationResourceProviderActions.CheckName}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{ResourceProviderActions.CheckName}", resourceName ) .Returns(Task.FromResult(expectedCheckResult)); @@ -660,7 +660,7 @@ public async Task CheckIndexingProfileNameAsync_ShouldReturnCheckResult() Assert.Equal(expectedCheckResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{VectorizationResourceProviderActions.CheckName}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{ResourceProviderActions.CheckName}", resourceName ); } @@ -695,7 +695,7 @@ public async Task FilterIndexingProfileAsync_ShouldReturnFilteredProfiles() _mockRestClient.Resources .ExecuteResourceActionAsync>( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{VectorizationResourceProviderActions.Filter}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{ResourceProviderActions.Filter}", resourceFilter ) .Returns(Task.FromResult(expectedProfiles)); @@ -707,7 +707,7 @@ public async Task FilterIndexingProfileAsync_ShouldReturnFilteredProfiles() Assert.Equal(expectedProfiles, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync>( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{VectorizationResourceProviderActions.Filter}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{ResourceProviderActions.Filter}", resourceFilter ); } @@ -722,7 +722,7 @@ public async Task PurgeIndexingProfileAsync_ShouldReturnPurgeResult() _mockRestClient.Resources .ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{profileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{profileName}/{ResourceProviderActions.Purge}", Arg.Any() ) .Returns(Task.FromResult(expectedPurgeResult)); @@ -734,7 +734,7 @@ public async Task PurgeIndexingProfileAsync_ShouldReturnPurgeResult() Assert.Equal(expectedPurgeResult, result); await _mockRestClient.Resources.Received(1).ExecuteResourceActionAsync( ResourceProviderNames.FoundationaLLM_Vectorization, - $"{VectorizationResourceTypeNames.IndexingProfiles}/{profileName}/{VectorizationResourceProviderActions.Purge}", + $"{VectorizationResourceTypeNames.IndexingProfiles}/{profileName}/{ResourceProviderActions.Purge}", Arg.Any() ); }