diff --git a/src/System Application/App/AI/app.json b/src/System Application/App/AI/app.json index a4de64d7de..aee58d9565 100644 --- a/src/System Application/App/AI/app.json +++ b/src/System Application/App/AI/app.json @@ -90,7 +90,7 @@ "idRanges": [ { "from": 7757, - "to": 7780 + "to": 7778 } ], "target": "OnPrem", diff --git a/src/System Application/App/AI/src/Azure AI Document Intelligence/ADIModelType.Enum.al b/src/System Application/App/AI/src/Azure AI Document Intelligence/ADIModelType.Enum.al deleted file mode 100644 index 00df30b255..0000000000 --- a/src/System Application/App/AI/src/Azure AI Document Intelligence/ADIModelType.Enum.al +++ /dev/null @@ -1,29 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ -namespace System.AI.DocumentIntelligence; - -/// -/// The supported model types for Azure Document Intelligence. -/// -enum 7779 "ADI Model Type" -{ - Access = Public; - Extensible = false; - - /// - /// Invoice model type. - /// - value(0; Invoice) - { - } - - /// - /// Receipt model type. - /// - value(1; Receipt) - { - } - -} \ No newline at end of file diff --git a/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDIImpl.Codeunit.al deleted file mode 100644 index cf70ecb106..0000000000 --- a/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDIImpl.Codeunit.al +++ /dev/null @@ -1,147 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ -namespace System.AI.DocumentIntelligence; - -using System.Telemetry; -using System; -using System.AI; - -/// -/// Azure Document Intelligence implementation. -/// -codeunit 7779 "Azure DI Impl." implements "AI Service Name" -{ - Access = Internal; - InherentEntitlements = X; - InherentPermissions = X; - - var - CopilotCapabilityImpl: Codeunit "Copilot Capability Impl"; - FeatureTelemetry: Codeunit "Feature Telemetry"; - AzureDocumentIntelligenceCapabilityTok: Label 'ADI', Locked = true; - TelemetryAnalyzeInvoiceFailureLbl: Label 'Analyze invoice failed.', Locked = true; - TelemetryAnalyzeInvoiceCompletedLbl: Label 'Analyze invoice completed.', Locked = true; - TelemetryAnalyzeReceiptFailureLbl: Label 'Analyze receipt failed.', Locked = true; - TelemetryAnalyzeReceiptCompletedLbl: Label 'Analyze receipt completed.', Locked = true; - GenerateRequestFailedErr: Label 'The request did not return a success status code.'; - AzureAiDocumentIntelligenceTxt: Label 'Azure AI Document Intelligence', Locked = true; - CapabilityNotEnabledErr: Label 'Copilot capability ''%1'' has not been enabled. Please contact your system administrator.', Comment = '%1 is the name of the Copilot Capability'; - - procedure SetCopilotCapability(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo) - begin - CopilotCapabilityImpl.SetCopilotCapability(Capability, CallerModuleInfo, Enum::"Azure AI Service Type"::"Azure Document Intelligence"); - end; - - procedure RegisterCopilotCapability(CopilotCapability: Enum "Copilot Capability"; CopilotAvailability: Enum "Copilot Availability"; LearnMoreUrl: Text[2048]; CallerModuleInfo: ModuleInfo) - begin - CopilotCapabilityImpl.RegisterCapability(CopilotCapability, CopilotAvailability, Enum::"Azure AI Service Type"::"Azure Document Intelligence", LearnMoreUrl, CallerModuleInfo); - end; - - /// - /// Analyze a single invoice. - /// - /// Data to analyze. - /// The module info of the caller. - /// The analyzed result. - procedure AnalyzeInvoice(Base64Data: Text; CallerModuleInfo: ModuleInfo) Result: Text - var - CustomDimensions: Dictionary of [Text, Text]; - begin - CopilotCapabilityImpl.CheckCapabilitySet(); - if not CopilotCapabilityImpl.IsCapabilityActive(CallerModuleInfo) then - Error(CapabilityNotEnabledErr, CopilotCapabilityImpl.GetCapabilityName()); - - CopilotCapabilityImpl.CheckCapabilityServiceType(Enum::"Azure AI Service Type"::"Azure Document Intelligence"); - CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); - - if not SendRequest(Base64Data, Enum::"ADI Model Type"::Invoice, CallerModuleInfo, Result) then begin - FeatureTelemetry.LogError('0000OLK', AzureDocumentIntelligenceCapabilityTok, TelemetryAnalyzeInvoiceFailureLbl, GetLastErrorText(), '', Enum::"AL Telemetry Scope"::All, CustomDimensions); - exit; - end; - - FeatureTelemetry.LogUsage('0000OLM', AzureDocumentIntelligenceCapabilityTok, TelemetryAnalyzeInvoiceCompletedLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); - end; - - /// - /// Analyze a single receipt. - /// - /// Data to analyze. - /// The module info of the caller. - /// The analyzed result. - procedure AnalyzeReceipt(Base64Data: Text; CallerModuleInfo: ModuleInfo) Result: Text - var - CustomDimensions: Dictionary of [Text, Text]; - begin - CopilotCapabilityImpl.CheckCapabilitySet(); - if not CopilotCapabilityImpl.IsCapabilityActive(CallerModuleInfo) then - Error(CapabilityNotEnabledErr, CopilotCapabilityImpl.GetCapabilityName()); - - CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); - - if not SendRequest(Base64Data, Enum::"ADI Model Type"::Receipt, CallerModuleInfo, Result) then begin - FeatureTelemetry.LogError('0000OLL', AzureDocumentIntelligenceCapabilityTok, TelemetryAnalyzeReceiptFailureLbl, GetLastErrorText(), '', Enum::"AL Telemetry Scope"::All, CustomDimensions); - exit; - end; - - FeatureTelemetry.LogUsage('0000OLN', AzureDocumentIntelligenceCapabilityTok, TelemetryAnalyzeReceiptCompletedLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); - end; - - [TryFunction] - [NonDebuggable] - local procedure SendRequest(Base64Data: Text; ModelType: Enum "ADI Model Type"; CallerModuleInfo: ModuleInfo; var Result: Text) - var - ALCopilotFunctions: DotNet ALCopilotFunctions; - ALCopilotCapability: DotNet ALCopilotCapability; - ALCopilotResponse: DotNet ALCopilotOperationResponse; - ErrorMsg: Text; - begin - ClearLastError(); - ALCopilotCapability := ALCopilotCapability.ALCopilotCapability(CallerModuleInfo.Publisher(), CallerModuleInfo.Id(), Format(CallerModuleInfo.AppVersion()), AzureDocumentIntelligenceCapabilityTok); - case ModelType of - Enum::"ADI Model Type"::Invoice: - ALCopilotResponse := ALCopilotFunctions.GenerateInvoiceIntelligence(GenerateJsonForSingleInput(Base64Data), ALCopilotCapability); - Enum::"ADI Model Type"::Receipt: - ALCopilotResponse := ALCopilotFunctions.GenerateReceiptIntelligence(GenerateJsonForSingleInput(Base64Data), ALCopilotCapability); - end; - ErrorMsg := ALCopilotResponse.ErrorText(); - if ErrorMsg <> '' then - Error(ErrorMsg); - - if not ALCopilotResponse.IsSuccess() then - Error(GenerateRequestFailedErr); - - Result := ALCopilotResponse.Result(); - end; - - local procedure GenerateJsonForSingleInput(Base64: Text): Text - var - JsonObject: JsonObject; - InputsObject: JsonObject; - InnerObject: JsonObject; - JsonText: Text; - begin - // Create the inner object with the base64Encoded property - InnerObject.Add('base64_encoded', Base64); - // Create the inputs object and add the inner object to it - InputsObject.Add('1', InnerObject); - // Create the main JSON object and add the inputs object to it - JsonObject.Add('inputs', InputsObject); - // Convert the JSON object to text - JsonObject.WriteTo(JsonText); - // Return the JSON text - exit(JsonText); - end; - - procedure GetServiceName(): Text[250] - begin - exit(AzureAiDocumentIntelligenceTxt); - end; - - procedure GetServiceId(): Code[50]; - begin - exit(AzureAiDocumentIntelligenceTxt); - end; - -} \ No newline at end of file diff --git a/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDocumentIntelligence.Codeunit.al b/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDocumentIntelligence.Codeunit.al deleted file mode 100644 index d9ca35a59b..0000000000 --- a/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDocumentIntelligence.Codeunit.al +++ /dev/null @@ -1,75 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ -namespace System.AI.DocumentIntelligence; - -using System.AI; - -/// -/// Provides functionality to invoke Azure Document Intelligence services. -/// -codeunit 7780 "Azure Document Intelligence" -{ - Access = Public; - InherentEntitlements = X; - InherentPermissions = X; - - var - AzureDIImpl: Codeunit "Azure DI Impl."; - - /// - /// Analyze the invoice. - /// - /// Data to analyze. - /// The analyzed result. - [Scope('OnPrem')] - procedure AnalyzeInvoice(Base64Data: Text): Text - var - CallerModuleInfo: ModuleInfo; - begin - NavApp.GetCallerModuleInfo(CallerModuleInfo); - exit(AzureDIImpl.AnalyzeInvoice(Base64Data, CallerModuleInfo)); - end; - - /// - /// Analyze the Receipt. - /// - /// Data to analyze. - /// The analyzed result. - [Scope('OnPrem')] - procedure AnalyzeReceipt(Base64Data: Text): Text - var - CallerModuleInfo: ModuleInfo; - begin - NavApp.GetCallerModuleInfo(CallerModuleInfo); - exit(AzureDIImpl.AnalyzeReceipt(Base64Data, CallerModuleInfo)); - end; - - /// - /// Register a capability for Azure Document Intelligence. - /// - /// The capability. - /// The availability. - /// The learn more url. - procedure RegisterCopilotCapability(CopilotCapability: Enum "Copilot Capability"; CopilotAvailability: Enum "Copilot Availability"; LearnMoreUrl: Text[2048]) - var - CallerModuleInfo: ModuleInfo; - begin - NavApp.GetCallerModuleInfo(CallerModuleInfo); - AzureDIImpl.RegisterCopilotCapability(CopilotCapability, CopilotAvailability, LearnMoreUrl, CallerModuleInfo); - end; - - /// - /// Sets the copilot capability that the API is running for. - /// - /// The copilot capability to set. - procedure SetCopilotCapability(CopilotCapability: Enum "Copilot Capability") - var - CallerModuleInfo: ModuleInfo; - begin - NavApp.GetCallerModuleInfo(CallerModuleInfo); - AzureDIImpl.SetCopilotCapability(CopilotCapability, CallerModuleInfo); - end; - -} \ No newline at end of file diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al index 21579c9bab..7992af7d3f 100644 --- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al +++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al @@ -270,7 +270,6 @@ codeunit 7771 "Azure OpenAI" NavApp.GetCallerModuleInfo(CallerModuleInfo); AzureOpenAIImpl.SetCopilotCapability(CopilotCapability, CallerModuleInfo); end; - #if not CLEAN24 /// /// Gets the approximate token count for the input. diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al index 89bfadd8c1..1e03e39e13 100644 --- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al +++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al @@ -8,10 +8,11 @@ using System; using System.Azure.Identity; using System.Azure.KeyVault; using System.Environment; +using System.Globalization; using System.Privacy; using System.Telemetry; -codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" +codeunit 7772 "Azure OpenAI Impl" { Access = Internal; InherentEntitlements = X; @@ -19,6 +20,8 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" Permissions = tabledata "Copilot Settings" = r; var + CopilotSettings: Record "Copilot Settings"; + CopilotCapabilityCU: Codeunit "Copilot Capability"; CopilotCapabilityImpl: Codeunit "Copilot Capability Impl"; ChatCompletionsAOAIAuthorization: Codeunit "AOAI Authorization"; TextCompletionsAOAIAuthorization: Codeunit "AOAI Authorization"; @@ -32,10 +35,16 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" EmbeddingsFailedWithCodeErr: Label 'Embeddings failed to be generated.'; ChatCompletionsFailedWithCodeErr: Label 'Chat completions failed to be generated.'; AuthenticationNotConfiguredErr: Label 'The authentication was not configured.'; + CopilotNotEnabledErr: Label 'Copilot is not enabled. Please contact your system administrator.'; + CopilotCapabilityNotSetErr: Label 'Copilot capability has not been set.'; CapabilityBackgroundErr: Label 'Microsoft Copilot Capabilities are not allowed in the background.'; + CopilotDisabledForTenantErr: Label 'Copilot is not enabled for the tenant. Please contact your system administrator.'; + CapabilityNotRegisteredErr: Label 'Copilot capability ''%1'' has not been registered by the module.', Comment = '%1 is the name of the Copilot Capability'; + CapabilityNotEnabledErr: Label 'Copilot capability ''%1'' has not been enabled. Please contact your system administrator.', Comment = '%1 is the name of the Copilot Capability'; MessagesMustContainJsonWordWhenResponseFormatIsJsonErr: Label 'The messages must contain the word ''json'' in some form, to use ''response format'' of type ''json_object''.'; EmptyMetapromptErr: Label 'The metaprompt has not been set, please provide a metaprompt.'; MetapromptLoadingErr: Label 'Metaprompt not found.'; + EnabledKeyTok: Label 'AOAI-Enabled', Locked = true; FunctionCallingFunctionNotFoundErr: Label 'Function call not found, %1.', Comment = '%1 is the name of the function'; AllowlistedTenantsAkvKeyTok: Label 'AOAI-Allow-1P-Auth', Locked = true; TelemetryGenerateTextCompletionLbl: Label 'Text completion generated.', Locked = true; @@ -43,27 +52,95 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" TelemetryGenerateChatCompletionLbl: Label 'Chat Completion generated.', Locked = true; TelemetryChatCompletionToolCallLbl: Label 'Tools called by chat completion.', Locked = true; TelemetryChatCompletionToolUsedLbl: Label 'Tools added to chat completion.', Locked = true; + TelemetrySetCapabilityLbl: Label 'Set Capability', Locked = true; + TelemetryCopilotCapabilityNotRegisteredLbl: Label 'Copilot capability not registered.', Locked = true; + TelemetryIsEnabledLbl: Label 'Is Enabled', Locked = true; + TelemetryUnableToCheckEnvironmentKVTxt: Label 'Unable to check if environment is allowed to run AOAI.', Locked = true; + TelemetryEnvironmentNotAllowedtoRunCopilotTxt: Label 'Copilot is not allowed on this environment.', Locked = true; TelemetryProhibitedCharactersTxt: Label 'Prohibited characters removed from the prompt.', Locked = true; TelemetryTokenCountLbl: Label 'Metaprompt token count: %1, Prompt token count: %2, Total token count: %3', Comment = '%1 is the number of tokens in the metaprompt, %2 is the number of tokens in the prompt, %3 is the total number of tokens', Locked = true; TelemetryMetapromptRetrievalErr: Label 'Unable to retrieve metaprompt from Azure Key Vault.', Locked = true; TelemetryFunctionCallingFailedErr: Label 'Function calling failed for function: %1', Comment = '%1 is the name of the function', Locked = true; TelemetryEmptyTenantIdErr: Label 'Empty or malformed tenant ID.', Locked = true; TelemetryTenantAllowlistedMsg: Label 'Current tenant allowlisted for first party auth.', Locked = true; - AzureOpenAiTxt: Label 'Azure OpenAI', Locked = true; procedure IsEnabled(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo): Boolean begin - exit(CopilotCapabilityImpl.IsCapabilityEnabled(Capability, CallerModuleInfo)); + exit(IsEnabled(Capability, false, CallerModuleInfo)); end; procedure IsEnabled(Capability: Enum "Copilot Capability"; Silent: Boolean; CallerModuleInfo: ModuleInfo): Boolean + var + CopilotNotAvailable: Page "Copilot Not Available"; begin - exit(CopilotCapabilityImpl.IsCapabilityEnabled(Capability, Silent, CallerModuleInfo)); + if not IsTenantAllowed() then begin + if not Silent then + Error(CopilotDisabledForTenantErr); // Copilot capabilities cannot be run on this environment. + + exit(false); + end; + + if not CopilotCapabilityCU.IsCapabilityActive(Capability, CallerModuleInfo.Id()) then begin + if not Silent then begin + CopilotNotAvailable.SetCopilotCapability(Capability); + CopilotNotAvailable.Run(); + end; + + exit(false); + end; + + exit(CheckPrivacyNoticeState(Silent, Capability)); end; - procedure SetCopilotCapability(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo) + [NonDebuggable] + local procedure IsTenantAllowed(): Boolean + var + EnvironmentInformation: Codeunit "Environment Information"; + AzureKeyVault: Codeunit "Azure Key Vault"; + AzureAdTenant: Codeunit "Azure AD Tenant"; + ModuleInfo: ModuleInfo; + BlockList: Text; begin - CopilotCapabilityImpl.SetCopilotCapability(Capability, CallerModuleInfo, Enum::"Azure AI Service Type"::"Azure OpenAI"); + if not EnvironmentInformation.IsSaaSInfrastructure() then + exit(true); + + NavApp.GetCurrentModuleInfo(ModuleInfo); + if ModuleInfo.Publisher <> 'Microsoft' then + exit(true); + + if (not AzureKeyVault.GetAzureKeyVaultSecret(EnabledKeyTok, BlockList)) or (BlockList.Trim() = '') then begin + FeatureTelemetry.LogError('0000KYC', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryIsEnabledLbl, TelemetryUnableToCheckEnvironmentKVTxt); + exit(false); + end; + + if BlockList.Contains(AzureAdTenant.GetAadTenantId()) then begin + FeatureTelemetry.LogError('0000LFP', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryIsEnabledLbl, TelemetryEnvironmentNotAllowedtoRunCopilotTxt); + exit(false); + end; + + exit(true); + end; + + local procedure CheckPrivacyNoticeState(Silent: Boolean; Capability: Enum "Copilot Capability"): Boolean + var + PrivacyNotice: Codeunit "Privacy Notice"; + CopilotNotAvailable: Page "Copilot Not Available"; + begin + case PrivacyNotice.GetPrivacyNoticeApprovalState(CopilotCapabilityImpl.GetAzureOpenAICategory(), false) of + Enum::"Privacy Notice Approval State"::Agreed: + exit(true); + Enum::"Privacy Notice Approval State"::Disagreed: + begin + if not Silent then begin + CopilotNotAvailable.SetCopilotCapability(Capability); + CopilotNotAvailable.Run(); + end; + + exit(false); + end; + else + exit(true); + end; end; procedure IsAuthorizationConfigured(ModelType: Enum "AOAI Model Type"; CallerModule: ModuleInfo): Boolean @@ -162,11 +239,11 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" begin GuiCheck(TextCompletionsAOAIAuthorization); - CopilotCapabilityImpl.CheckCapabilitySet(); - CopilotCapabilityImpl.CheckEnabled(CallerModuleInfo); + CheckCapabilitySet(); + CheckEnabled(CallerModuleInfo); CheckAuthorizationEnabled(TextCompletionsAOAIAuthorization, CallerModuleInfo); - CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); + AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); CheckTextCompletionMetaprompt(Metaprompt, CustomDimensions); UnwrappedPrompt := Metaprompt.Unwrap() + Prompt.Unwrap(); @@ -179,11 +256,11 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" SendTokenCountTelemetry(AOAIToken.GetGPT4TokenCount(Metaprompt), AOAIToken.GetGPT4TokenCount(Prompt), CustomDimensions); if not SendRequest(Enum::"AOAI Model Type"::"Text Completions", TextCompletionsAOAIAuthorization, PayloadText, AOAIOperationResponse, CallerModuleInfo) then begin - FeatureTelemetry.LogError('0000KVD', GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, CompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogError('0000KVD', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, CompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); exit; end; - FeatureTelemetry.LogUsage('0000KVL', GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogUsage('0000KVL', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); Result := AOAIOperationResponse.GetResult(); end; @@ -196,21 +273,21 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" begin GuiCheck(EmbeddingsAOAIAuthorization); - CopilotCapabilityImpl.CheckCapabilitySet(); - CopilotCapabilityImpl.CheckEnabled(CallerModuleInfo); + CheckCapabilitySet(); + CheckEnabled(CallerModuleInfo); CheckAuthorizationEnabled(EmbeddingsAOAIAuthorization, CallerModuleInfo); Payload.Add('input', Input.Unwrap()); Payload.WriteTo(PayloadText); - CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); + AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); SendTokenCountTelemetry(0, AOAIToken.GetAdaTokenCount(Input), CustomDimensions); if not SendRequest(Enum::"AOAI Model Type"::Embeddings, EmbeddingsAOAIAuthorization, PayloadText, AOAIOperationResponse, CallerModuleInfo) then begin - FeatureTelemetry.LogError('0000KVE', GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, EmbeddingsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogError('0000KVE', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, EmbeddingsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); exit; end; - FeatureTelemetry.LogUsage('0000KVM', GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogUsage('0000KVM', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); exit(ProcessEmbeddingResponse(AOAIOperationResponse)); end; @@ -250,10 +327,10 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" begin GuiCheck(ChatCompletionsAOAIAuthorization); - CopilotCapabilityImpl.CheckCapabilitySet(); - CopilotCapabilityImpl.CheckEnabled(CallerModuleInfo); + CheckCapabilitySet(); + CheckEnabled(CallerModuleInfo); CheckAuthorizationEnabled(ChatCompletionsAOAIAuthorization, CallerModuleInfo); - CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); + AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); AOAIChatCompletionParams.AddChatCompletionsParametersToPayload(Payload); Payload.Add('messages', ChatMessages.AssembleHistory(MetapromptTokenCount, PromptTokenCount)); @@ -279,13 +356,13 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" SendTokenCountTelemetry(MetapromptTokenCount, PromptTokenCount, CustomDimensions); if not SendRequest(Enum::"AOAI Model Type"::"Chat Completions", ChatCompletionsAOAIAuthorization, PayloadText, AOAIOperationResponse, CallerModuleInfo) then begin - FeatureTelemetry.LogError('0000KVF', GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, ChatCompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogError('0000KVF', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, ChatCompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); exit; end; ProcessChatCompletionResponse(ChatMessages, AOAIOperationResponse, CallerModuleInfo); - FeatureTelemetry.LogUsage('0000KVN', GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogUsage('0000KVN', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions); if (AOAIOperationResponse.GetFunctionResponses().Count() > 0) and (ChatMessages.GetToolInvokePreference() = Enum::"AOAI Tool Invoke Preference"::Automatic) then GenerateChatCompletion(ChatMessages, AOAIChatCompletionParams, AOAIOperationResponse, CallerModuleInfo); @@ -341,10 +418,10 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" AOAIOperationResponse.AddFunctionResponse(AOAIFunctionResponse); end; - CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); + AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo); foreach AOAIFunctionResponse in AOAIOperationResponse.GetFunctionResponses() do if not AOAIFunctionResponse.IsSuccess() then - FeatureTelemetry.LogError('0000MTB', GetAzureOpenAICategory(), StrSubstNo(TelemetryFunctionCallingFailedErr, AOAIFunctionResponse.GetFunctionName()), AOAIFunctionResponse.GetError(), AOAIFunctionResponse.GetErrorCallstack(), Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogError('0000MTB', CopilotCapabilityImpl.GetAzureOpenAICategory(), StrSubstNo(TelemetryFunctionCallingFailedErr, AOAIFunctionResponse.GetFunctionName()), AOAIFunctionResponse.GetError(), AOAIFunctionResponse.GetErrorCallstack(), Enum::"AL Telemetry Scope"::All, CustomDimensions); if ChatMessages.GetToolInvokePreference() in [Enum::"AOAI Tool Invoke Preference"::"Invoke Tools Only", Enum::"AOAI Tool Invoke Preference"::Automatic] then AOAIOperationResponse.AppendFunctionResponsesToChatMessages(ChatMessages); @@ -454,7 +531,7 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" ALCopilotAuthorization := ALCopilotAuthorization.Create(AOAIAuthorization.GetEndpoint(), AOAIAuthorization.GetDeployment(), AOAIAuthorization.GetApiKey()); end; - ALCopilotCapability := ALCopilotCapability.ALCopilotCapability(CallerModuleInfo.Publisher(), CallerModuleInfo.Id(), Format(CallerModuleInfo.AppVersion()), CopilotCapabilityImpl.GetCapabilityName()); + ALCopilotCapability := ALCopilotCapability.ALCopilotCapability(CallerModuleInfo.Publisher(), CallerModuleInfo.Id(), Format(CallerModuleInfo.AppVersion()), GetCapabilityName()); case ModelType of Enum::"AOAI Model Type"::"Text Completions": @@ -479,6 +556,22 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" Error(GenerateRequestFailedErr); end; + local procedure GetCapabilityName(): Text + var + CapabilityIndex: Integer; + CapabilityName: Text; + begin + CheckCapabilitySet(); + + CapabilityIndex := CopilotSettings.Capability.Ordinals.IndexOf(CopilotSettings.Capability.AsInteger()); + CapabilityName := CopilotSettings.Capability.Names.Get(CapabilityIndex); + + if CapabilityName.Trim() = '' then + exit(Format(CopilotSettings.Capability, 0, 9)); + + exit(CapabilityName); + end; + local procedure SendTokenCountTelemetry(Metaprompt: Integer; Prompt: Integer; CustomDimensions: Dictionary of [Text, Text]) begin Telemetry.LogMessage('0000LT4', StrSubstNo(TelemetryTokenCountLbl, Metaprompt, Prompt, Metaprompt + Prompt), Verbosity::Normal, DataClassification::OrganizationIdentifiableInformation, Enum::"AL Telemetry Scope"::All, CustomDimensions); @@ -495,12 +588,70 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" Error(CapabilityBackgroundErr); end; + local procedure AddTelemetryCustomDimensions(var CustomDimensions: Dictionary of [Text, Text]; CallerModuleInfo: ModuleInfo) + var + Language: Codeunit Language; + SavedGlobalLanguageId: Integer; + begin + SavedGlobalLanguageId := GlobalLanguage(); + GlobalLanguage(Language.GetDefaultApplicationLanguageId()); + + CustomDimensions.Add('Capability', Format(CopilotSettings.Capability)); + CustomDimensions.Add('AppId', Format(CopilotSettings."App Id")); + CustomDimensions.Add('Publisher', CallerModuleInfo.Publisher); + CustomDimensions.Add('UserLanguage', Format(GlobalLanguage())); + + GlobalLanguage(SavedGlobalLanguageId); + end; + + procedure SetCopilotCapability(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo) + var + CopilotTelemetry: Codeunit "Copilot Telemetry"; + Language: Codeunit Language; + SavedGlobalLanguageId: Integer; + CustomDimensions: Dictionary of [Text, Text]; + ErrorMessage: Text; + begin + if not CopilotCapabilityCU.IsCapabilityRegistered(Capability, CallerModuleInfo.Id()) then begin + SavedGlobalLanguageId := GlobalLanguage(); + GlobalLanguage(Language.GetDefaultApplicationLanguageId()); + CustomDimensions.Add('Capability', Format(Capability)); + CustomDimensions.Add('AppId', Format(CallerModuleInfo.Id())); + GlobalLanguage(SavedGlobalLanguageId); + + FeatureTelemetry.LogError('0000LFN', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetrySetCapabilityLbl, TelemetryCopilotCapabilityNotRegisteredLbl, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); + ErrorMessage := StrSubstNo(CapabilityNotRegisteredErr, Capability); + Error(ErrorMessage); + end; + + CopilotSettings.ReadIsolation(IsolationLevel::ReadCommitted); + CopilotSettings.SetLoadFields(Status); + CopilotSettings.Get(Capability, CallerModuleInfo.Id()); + if CopilotSettings.Status = Enum::"Copilot Status"::Inactive then begin + ErrorMessage := StrSubstNo(CapabilityNotEnabledErr, Capability); + Error(ErrorMessage); + end; + CopilotTelemetry.SetCopilotCapability(Capability, CallerModuleInfo.Id()); + end; + + local procedure CheckEnabled(CallerModuleInfo: ModuleInfo) + begin + if not IsEnabled(CopilotSettings.Capability, true, CallerModuleInfo) then + Error(CopilotNotEnabledErr); + end; + local procedure CheckAuthorizationEnabled(AOAIAuthorization: Codeunit "AOAI Authorization"; CallerModuleInfo: ModuleInfo) begin if not AOAIAuthorization.IsConfigured(CallerModuleInfo) then Error(AuthenticationNotConfiguredErr); end; + local procedure CheckCapabilitySet() + begin + if CopilotSettings.Capability.AsInteger() = 0 then + Error(CopilotCapabilityNotSetErr); + end; + [NonDebuggable] procedure RemoveProhibitedCharacters(Prompt: Text) Result: Text begin @@ -541,7 +692,7 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" ModuleInfo: ModuleInfo; begin if Metaprompt.Unwrap().Trim() = '' then begin - FeatureTelemetry.LogError('0000LO8', GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, EmptyMetapromptErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); + FeatureTelemetry.LogError('0000LO8', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, EmptyMetapromptErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); NavApp.GetCurrentModuleInfo(ModuleInfo); if ModuleInfo.Publisher = 'Microsoft' then @@ -600,39 +751,15 @@ codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name" EntraTenantIdAsText := AzureAdTenant.GetAadTenantId(); if (EntraTenantIdAsText = '') or not Evaluate(EntraTenantIdAsGuid, EntraTenantIdAsText) or IsNullGuid(EntraTenantIdAsGuid) then begin - Session.LogMessage('0000MLN', TelemetryEmptyTenantIdErr, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GetAzureOpenAICategory()); + Session.LogMessage('0000MLN', TelemetryEmptyTenantIdErr, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory()); exit(false); end; if not AllowlistedTenants.Contains(EntraTenantIdAsText) then exit(false); - Session.LogMessage('0000MLE', TelemetryTenantAllowlistedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GetAzureOpenAICategory()); + Session.LogMessage('0000MLE', TelemetryTenantAllowlistedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory()); exit(true); end; - procedure GetAzureOpenAICategory(): Code[50] - begin - exit(AzureOpenAiTxt); - end; - - procedure GetServiceName(): Text[250]; - begin - exit(AzureOpenAiTxt); - end; - - procedure GetServiceId(): Code[50]; - begin - exit(AzureOpenAiTxt); - end; - - [EventSubscriber(ObjectType::Codeunit, Codeunit::"Privacy Notice", 'OnRegisterPrivacyNotices', '', false, false)] - local procedure CreatePrivacyNoticeRegistrations(var TempPrivacyNotice: Record "Privacy Notice" temporary) - begin - TempPrivacyNotice.Init(); - TempPrivacyNotice.ID := GetAzureOpenAICategory(); - TempPrivacyNotice."Integration Service Name" := GetServiceName(); - if not TempPrivacyNotice.Insert() then; - end; - } \ No newline at end of file diff --git a/src/System Application/App/AI/src/Copilot/AzureAIServiceType.Enum.al b/src/System Application/App/AI/src/Copilot/AzureAIServiceType.Enum.al deleted file mode 100644 index 3705990822..0000000000 --- a/src/System Application/App/AI/src/Copilot/AzureAIServiceType.Enum.al +++ /dev/null @@ -1,33 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ -namespace System.AI; -using System.AI.DocumentIntelligence; - -/// -/// The supported service types for Azure AI. -/// -enum 7778 "Azure AI Service Type" implements "AI Service Name" -{ - Access = Public; - Extensible = false; - - /// - /// Azure OpenAI service type. - /// - value(0; "Azure OpenAI") - { - Caption = 'Azure OpenAI'; - Implementation = "AI Service Name" = "Azure OpenAI Impl"; - } - - /// - /// Azure Document Intelligence service type. - /// - value(1; "Azure Document Intelligence") - { - Caption = 'Azure Document Intelligence'; - Implementation = "AI Service Name" = "Azure DI Impl."; - } -} \ No newline at end of file diff --git a/src/System Application/App/AI/src/Copilot/CopilotAICapabilities.Page.al b/src/System Application/App/AI/src/Copilot/CopilotAICapabilities.Page.al index 2ecd9c7ed1..cfda74d386 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotAICapabilities.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotAICapabilities.Page.al @@ -257,7 +257,7 @@ page 7775 "Copilot AI Capabilities" CopilotCapabilityImpl.CheckGeoAndEUDB(WithinGeo, WithinEUDB); - case PrivacyNotice.GetPrivacyNoticeApprovalState(AzureOpenAIImpl.GetAzureOpenAICategory(), false) of + case PrivacyNotice.GetPrivacyNoticeApprovalState(CopilotCapabilityImpl.GetAzureOpenAICategory(), false) of Enum::"Privacy Notice Approval State"::Agreed: AllowDataMovement := true; Enum::"Privacy Notice Approval State"::Disagreed: @@ -297,16 +297,15 @@ page 7775 "Copilot AI Capabilities" CopilotSettings: Record "Copilot Settings"; begin CopilotSettings.SetRange(Availability, Enum::"Copilot Availability"::"Early Preview"); - CopilotSettings.SetRange("Service Type", Enum::"Azure AI Service Type"::"Azure OpenAI"); exit(not CopilotSettings.IsEmpty()); end; local procedure UpdateAllowDataMovement() begin if AllowDataMovement then - PrivacyNotice.SetApprovalState(AzureOpenAIImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Agreed) + PrivacyNotice.SetApprovalState(CopilotCapabilityImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Agreed) else - PrivacyNotice.SetApprovalState(AzureOpenAIImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Disagreed); + PrivacyNotice.SetApprovalState(CopilotCapabilityImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Disagreed); CurrPage.GenerallyAvailableCapabilities.Page.SetDataMovement(AllowDataMovement); CurrPage.PreviewCapabilities.Page.SetDataMovement(AllowDataMovement); @@ -320,7 +319,6 @@ page 7775 "Copilot AI Capabilities" end; var - AzureOpenAIImpl: Codeunit "Azure OpenAI Impl"; CopilotCapabilityImpl: Codeunit "Copilot Capability Impl"; PrivacyNotice: Codeunit "Privacy Notice"; WithinEUDBArea: Boolean; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al index f044a41d1f..f322325fb9 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al @@ -16,7 +16,7 @@ page 7770 "Copilot Cap. Early Preview" Editable = false; Extensible = false; SourceTable = "Copilot Settings"; - SourceTableView = where(Availability = const("Early Preview"), "Service Type" = const("Azure AI Service Type"::"Azure OpenAI")); + SourceTableView = where(Availability = const("Early Preview")); Permissions = tabledata "Copilot Settings" = rm; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al index 241e75d182..b76b8e4350 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al @@ -16,7 +16,7 @@ page 7774 "Copilot Capabilities GA" Editable = false; Extensible = false; SourceTable = "Copilot Settings"; - SourceTableView = where(Availability = const("Generally Available"), "Service Type" = const("Azure AI Service Type"::"Azure OpenAI")); + SourceTableView = where(Availability = const("Generally Available")); Permissions = tabledata "Copilot Settings" = rm; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al index 5c528a18a6..38e90454e1 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al @@ -16,7 +16,7 @@ page 7773 "Copilot Capabilities Preview" Editable = false; Extensible = false; SourceTable = "Copilot Settings"; - SourceTableView = where(Availability = const(Preview), "Service Type" = const("Azure AI Service Type"::"Azure OpenAI")); + SourceTableView = where(Availability = const(Preview)); Permissions = tabledata "Copilot Settings" = rm; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al index 1989341ee8..366a56b901 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al @@ -6,7 +6,6 @@ namespace System.AI; using System; using System.Azure.Identity; -using System.Azure.KeyVault; using System.Environment; using System.Environment.Configuration; using System.Globalization; @@ -22,23 +21,11 @@ codeunit 7774 "Copilot Capability Impl" Permissions = tabledata "Copilot Settings" = rimd; var - CopilotSettings: Record "Copilot Settings"; - FeatureTelemetry: Codeunit "Feature Telemetry"; Telemetry: Codeunit Telemetry; CopilotCategoryLbl: Label 'Copilot', Locked = true; + AzureOpenAiTxt: Label 'Azure OpenAI', Locked = true; AlreadyRegisteredErr: Label 'Capability has already been registered.'; NotRegisteredErr: Label 'Copilot capability has not been registered by the module.'; - CapabilityNotRegisteredErr: Label 'Copilot capability ''%1'' has not been registered by the module.', Comment = '%1 is the name of the Copilot Capability'; - CapabilityNotEnabledErr: Label 'Copilot capability ''%1'' has not been enabled. Please contact your system administrator.', Comment = '%1 is the name of the Copilot Capability'; - TelemetrySetCapabilityLbl: Label 'Set Capability', Locked = true; - CopilotNotEnabledErr: Label 'Copilot is not enabled. Please contact your system administrator.'; - CopilotCapabilityNotSetErr: Label 'Copilot capability has not been set.'; - CopilotDisabledForTenantErr: Label 'Copilot is not enabled for the tenant. Please contact your system administrator.'; - TelemetryIsEnabledLbl: Label 'Is Enabled', Locked = true; - TelemetryUnableToCheckEnvironmentKVTxt: Label 'Unable to check if environment is allowed to run AOAI.', Locked = true; - TelemetryEnvironmentNotAllowedtoRunCopilotTxt: Label 'Copilot is not allowed on this environment.', Locked = true; - EnabledKeyTok: Label 'AOAI-Enabled', Locked = true; - TelemetryCopilotCapabilityNotRegisteredLbl: Label 'Copilot capability not registered.', Locked = true; TelemetryRegisteredNewCopilotCapabilityLbl: Label 'New copilot capability registered.', Locked = true; TelemetryModifiedCopilotCapabilityLbl: Label 'Copilot capability modified', Locked = true; TelemetryUnregisteredCopilotCapabilityLbl: Label 'Copilot capability unregistered.', Locked = true; @@ -51,25 +38,19 @@ codeunit 7774 "Copilot Capability Impl" end; procedure RegisterCapability(CopilotCapability: Enum "Copilot Capability"; CopilotAvailability: Enum "Copilot Availability"; LearnMoreUrl: Text[2048]; CallerModuleInfo: ModuleInfo) - begin - RegisterCapability(CopilotCapability, CopilotAvailability, Enum::"Azure AI Service Type"::"Azure OpenAI", LearnMoreUrl, CallerModuleInfo); - end; - - procedure RegisterCapability(CopilotCapability: Enum "Copilot Capability"; CopilotAvailability: Enum "Copilot Availability"; AzureAIServiceType: Enum "Azure AI Service Type"; LearnMoreUrl: Text[2048]; CallerModuleInfo: ModuleInfo) var + CopilotSettings: Record "Copilot Settings"; CustomDimensions: Dictionary of [Text, Text]; begin if IsCapabilityRegistered(CopilotCapability, CallerModuleInfo) then Error(AlreadyRegisteredErr); - Clear(CopilotSettings); CopilotSettings.Init(); CopilotSettings.Capability := CopilotCapability; CopilotSettings."App Id" := CallerModuleInfo.Id(); CopilotSettings.Publisher := CopyStr(CallerModuleInfo.Publisher, 1, MaxStrLen(CopilotSettings.Publisher)); CopilotSettings.Availability := CopilotAvailability; CopilotSettings."Learn More Url" := LearnMoreUrl; - CopilotSettings."Service Type" := AzureAIServiceType; if CopilotSettings.Availability = Enum::"Copilot Availability"::"Early Preview" then CopilotSettings.Status := Enum::"Copilot Status"::Inactive else @@ -81,40 +62,9 @@ codeunit 7774 "Copilot Capability Impl" Telemetry.LogMessage('0000LDV', TelemetryRegisteredNewCopilotCapabilityLbl, Verbosity::Normal, DataClassification::OrganizationIdentifiableInformation, Enum::"AL Telemetry Scope"::All, CustomDimensions); end; - procedure SetCopilotCapability(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo; AIServiceType: Enum "Azure AI Service Type") - var - CopilotTelemetry: Codeunit "Copilot Telemetry"; - Language: Codeunit Language; - IAIServicename: Interface "AI Service Name"; - SavedGlobalLanguageId: Integer; - CustomDimensions: Dictionary of [Text, Text]; - ErrorMessage: Text; - begin - if not IsCapabilityRegistered(Capability, CallerModuleInfo.Id()) then begin - SavedGlobalLanguageId := GlobalLanguage(); - GlobalLanguage(Language.GetDefaultApplicationLanguageId()); - CustomDimensions.Add('Capability', Format(Capability)); - CustomDimensions.Add('AppId', Format(CallerModuleInfo.Id())); - GlobalLanguage(SavedGlobalLanguageId); - - IAIServicename := AIServiceType; - FeatureTelemetry.LogError('0000LFN', IAIServicename.GetServiceName(), TelemetrySetCapabilityLbl, TelemetryCopilotCapabilityNotRegisteredLbl, '', Enum::"AL Telemetry Scope"::All, CustomDimensions); - ErrorMessage := StrSubstNo(CapabilityNotRegisteredErr, Capability); - Error(ErrorMessage); - end; - - CopilotSettings.ReadIsolation(IsolationLevel::ReadCommitted); - CopilotSettings.SetLoadFields(Status); - CopilotSettings.Get(Capability, CallerModuleInfo.Id()); - if CopilotSettings.Status = Enum::"Copilot Status"::Inactive then begin - ErrorMessage := StrSubstNo(CapabilityNotEnabledErr, Capability); - Error(ErrorMessage); - end; - CopilotTelemetry.SetCopilotCapability(Capability, CallerModuleInfo.Id()); - end; - procedure ModifyCapability(CopilotCapability: Enum "Copilot Capability"; CopilotAvailability: Enum "Copilot Availability"; LearnMoreUrl: Text[2048]; CallerModuleInfo: ModuleInfo) var + CopilotSettings: Record "Copilot Settings"; CustomDimensions: Dictionary of [Text, Text]; begin if not IsCapabilityRegistered(CopilotCapability, CallerModuleInfo) then @@ -138,6 +88,7 @@ codeunit 7774 "Copilot Capability Impl" procedure UnregisterCapability(CopilotCapability: Enum "Copilot Capability"; var CallerModuleInfo: ModuleInfo) var + CopilotSettings: Record "Copilot Settings"; CustomDimensions: Dictionary of [Text, Text]; begin if not IsCapabilityRegistered(CopilotCapability, CallerModuleInfo) then @@ -159,6 +110,8 @@ codeunit 7774 "Copilot Capability Impl" end; procedure IsCapabilityRegistered(CopilotCapability: Enum "Copilot Capability"; AppId: Guid): Boolean + var + CopilotSettings: Record "Copilot Settings"; begin CopilotSettings.ReadIsolation(IsolationLevel::ReadCommitted); CopilotSettings.SetRange("Capability", CopilotCapability); @@ -166,11 +119,6 @@ codeunit 7774 "Copilot Capability Impl" exit(not CopilotSettings.IsEmpty()); end; - procedure IsCapabilityActive(CallerModuleInfo: ModuleInfo): Boolean - begin - exit(IsCapabilityActive(CopilotSettings.Capability, CallerModuleInfo.Id())); - end; - procedure IsCapabilityActive(CopilotCapability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo): Boolean begin exit(IsCapabilityActive(CopilotCapability, CallerModuleInfo.Id())); @@ -178,6 +126,7 @@ codeunit 7774 "Copilot Capability Impl" procedure IsCapabilityActive(CopilotCapability: Enum "Copilot Capability"; AppId: Guid): Boolean var + CopilotSettings: Record "Copilot Settings"; CopilotCapabilityCU: Codeunit "Copilot Capability"; PrivacyNotice: Codeunit "Privacy Notice"; RequiredPrivacyNotices: List of [Code[50]]; @@ -201,140 +150,6 @@ codeunit 7774 "Copilot Capability Impl" exit(true); end; - procedure GetCapabilityName(): Text - var - CapabilityIndex: Integer; - CapabilityName: Text; - begin - CheckCapabilitySet(); - - CapabilityIndex := CopilotSettings.Capability.Ordinals.IndexOf(CopilotSettings.Capability.AsInteger()); - CapabilityName := CopilotSettings.Capability.Names.Get(CapabilityIndex); - - if CapabilityName.Trim() = '' then - exit(Format(CopilotSettings.Capability, 0, 9)); - - exit(CapabilityName); - end; - - procedure CheckCapabilitySet() - begin - if CopilotSettings.Capability.AsInteger() = 0 then - Error(CopilotCapabilityNotSetErr); - end; - - procedure CheckCapabilityServiceType(ServiceType: Enum "Azure AI Service Type") - begin - if CopilotSettings."Service Type" <> ServiceType then - Error(CopilotCapabilityNotSetErr); - end; - - procedure CheckEnabled(CallerModuleInfo: ModuleInfo) - begin - if not IsCapabilityEnabled(CopilotSettings.Capability, true, CallerModuleInfo) then - Error(CopilotNotEnabledErr); - end; - - procedure IsCapabilityEnabled(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo): Boolean - begin - exit(IsCapabilityEnabled(Capability, false, CallerModuleInfo)); - end; - - procedure IsCapabilityEnabled(Capability: Enum "Copilot Capability"; Silent: Boolean; CallerModuleInfo: ModuleInfo): Boolean - var - CopilotNotAvailable: Page "Copilot Not Available"; - begin - if not IsTenantAllowedToUseAOAI() then begin - if not Silent then - Error(CopilotDisabledForTenantErr); // Copilot capabilities cannot be run on this environment. - - exit(false); - end; - - if not IsCapabilityActive(Capability, CallerModuleInfo.Id()) then begin - if not Silent then begin - CopilotNotAvailable.SetCopilotCapability(Capability); - CopilotNotAvailable.Run(); - end; - - exit(false); - end; - - exit(CheckPrivacyNoticeState(Silent, Capability)); - end; - - [NonDebuggable] - local procedure IsTenantAllowedToUseAOAI(): Boolean - var - EnvironmentInformation: Codeunit "Environment Information"; - AzureOpenAIImpl: Codeunit "Azure OpenAI Impl"; - AzureKeyVault: Codeunit "Azure Key Vault"; - AzureAdTenant: Codeunit "Azure AD Tenant"; - ModuleInfo: ModuleInfo; - BlockList, TelemtryTok : Text; - begin - if not EnvironmentInformation.IsSaaSInfrastructure() then - exit(true); - - NavApp.GetCurrentModuleInfo(ModuleInfo); - if ModuleInfo.Publisher <> 'Microsoft' then - exit(true); - - TelemtryTok := AzureOpenAIImpl.GetAzureOpenAICategory(); - if (not AzureKeyVault.GetAzureKeyVaultSecret(EnabledKeyTok, BlockList)) or (BlockList.Trim() = '') then begin - FeatureTelemetry.LogError('0000KYC', TelemtryTok, TelemetryIsEnabledLbl, TelemetryUnableToCheckEnvironmentKVTxt); - exit(false); - end; - - if BlockList.Contains(AzureAdTenant.GetAadTenantId()) then begin - FeatureTelemetry.LogError('0000LFP', TelemtryTok, TelemetryIsEnabledLbl, TelemetryEnvironmentNotAllowedtoRunCopilotTxt); - exit(false); - end; - - exit(true); - end; - - local procedure CheckPrivacyNoticeState(Silent: Boolean; Capability: Enum "Copilot Capability"): Boolean - var - PrivacyNotice: Codeunit "Privacy Notice"; - AzureOpenAIImpl: Codeunit "Azure OpenAI Impl"; - CopilotNotAvailable: Page "Copilot Not Available"; - PrivacyNoticeApprovalState: Enum "Privacy Notice Approval State"; - begin - PrivacyNoticeApprovalState := PrivacyNotice.GetPrivacyNoticeApprovalState(AzureOpenAIImpl.GetAzureOpenAICategory(), false); - case PrivacyNoticeApprovalState of - Enum::"Privacy Notice Approval State"::Agreed: - exit(true); - Enum::"Privacy Notice Approval State"::Disagreed: - begin - if not Silent then begin - CopilotNotAvailable.SetCopilotCapability(Capability); - CopilotNotAvailable.Run(); - end; - - exit(false); - end; - else - exit(true); - end; - end; - - procedure AddTelemetryCustomDimensions(var CustomDimensions: Dictionary of [Text, Text]; CallerModuleInfo: ModuleInfo) - var - Language: Codeunit Language; - SavedGlobalLanguageId: Integer; - begin - SavedGlobalLanguageId := GlobalLanguage(); - GlobalLanguage(Language.GetDefaultApplicationLanguageId()); - - CustomDimensions.Add('Capability', Format(CopilotSettings.Capability)); - CustomDimensions.Add('AppId', Format(CopilotSettings."App Id")); - CustomDimensions.Add('Publisher', CallerModuleInfo.Publisher); - CustomDimensions.Add('UserLanguage', Format(GlobalLanguage())); - - GlobalLanguage(SavedGlobalLanguageId); - end; - procedure SendActivateTelemetry(CopilotCapability: Enum "Copilot Capability"; AppId: Guid) var CustomDimensions: Dictionary of [Text, Text]; @@ -360,6 +175,11 @@ codeunit 7774 "Copilot Capability Impl" GlobalLanguage(SavedGlobalLanguageId); end; + procedure GetAzureOpenAICategory(): Code[50] + begin + exit(AzureOpenAiTxt); + end; + procedure GetCopilotCategory(): Code[50] begin exit(CopilotCategoryLbl); @@ -422,6 +242,15 @@ codeunit 7774 "Copilot Capability Impl" GuidedExperience.ResetAssistedSetup(ObjectType::Page, Page::"Copilot AI Capabilities"); end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Privacy Notice", 'OnRegisterPrivacyNotices', '', false, false)] + local procedure CreatePrivacyNoticeRegistrations(var TempPrivacyNotice: Record "Privacy Notice" temporary) + begin + TempPrivacyNotice.Init(); + TempPrivacyNotice.ID := AzureOpenAiTxt; + TempPrivacyNotice."Integration Service Name" := AzureOpenAiTxt; + if not TempPrivacyNotice.Insert() then; + end; + [EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", 'GetCopilotCapabilityStatus', '', false, false)] local procedure GetCopilotCapabilityStatus(Capability: Integer; var IsEnabled: Boolean; AppId: Guid; Silent: Boolean) var diff --git a/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al b/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al index e86861ff8f..9ee6c06bfb 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al +++ b/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al @@ -44,10 +44,6 @@ table 7775 "Copilot Settings" { DataClassification = SystemMetadata; } - field(7; "Service Type"; Enum "Azure AI Service Type") - { - DataClassification = SystemMetadata; - } } keys diff --git a/src/System Application/App/AI/src/Copilot/Interfaces/AIServiceName.Interface.al b/src/System Application/App/AI/src/Copilot/Interfaces/AIServiceName.Interface.al deleted file mode 100644 index f7ac245c07..0000000000 --- a/src/System Application/App/AI/src/Copilot/Interfaces/AIServiceName.Interface.al +++ /dev/null @@ -1,25 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ -namespace System.AI; - -/// -/// Interface for providing naming information for a given AI service. -/// -interface "AI Service Name" -{ - - /// - /// Get the name of the service. - /// - /// The name of the service. - procedure GetServiceName(): Text[250]; - - /// - /// Get the id of the service. Will often be the service name in Code form. - /// - /// The id of the service. - procedure GetServiceId(): Code[50]; - -} \ No newline at end of file diff --git a/src/System Application/Test/AI/src/AzureDITest.Codeunit.al b/src/System Application/Test/AI/src/AzureDITest.Codeunit.al deleted file mode 100644 index 767f6b6dd5..0000000000 --- a/src/System Application/Test/AI/src/AzureDITest.Codeunit.al +++ /dev/null @@ -1,95 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.Test.AI; - -using System.AI; -using System.TestLibraries.AI; -using System.TestLibraries.Environment; -using System.AI.DocumentIntelligence; -using System.TestLibraries.Utilities; - -codeunit 132685 "Azure DI Test" -{ - Subtype = Test; - - var - CopilotTestLibrary: Codeunit "Copilot Test Library"; - EnvironmentInfoTestLibrary: Codeunit "Environment Info Test Library"; - LibraryAssert: Codeunit "Library Assert"; - - [Test] - procedure TestSetCopilotCapabilityInactive() - var - AzureDI: Codeunit "Azure Document Intelligence"; - begin - - // [GIVEN] Capability is set - RegisterCapability(Enum::"Copilot Capability"::"Text Capability"); - CopilotTestLibrary.SetCopilotStatus(Enum::"Copilot Capability"::"Text Capability", GetModuleAppId(), Enum::"Copilot Status"::Inactive); - - // [WHEN] SetCopilotCapability is called - asserterror AzureDI.SetCopilotCapability(Enum::"Copilot Capability"::"Text Capability"); - - // [THEN] SetCopilotCapability returns an error - LibraryAssert.ExpectedError('Copilot capability ''Text Capability'' has not been enabled. Please contact your system administrator.'); - end; - - [Test] - procedure AnalyzeInvoiceCopilotCapabilityNotSet() - var - AzureDI: Codeunit "Azure Document Intelligence"; - begin - // [SCENARIO] AnalyzeInvoice returns an error when capability is not set - - EnvironmentInfoTestLibrary.SetTestabilitySoftwareAsAService(false); - - // [WHEN] AnalyzeInvoice is called - asserterror AzureDI.AnalyzeInvoice('Text'); - - // [THEN] AnalyzeInvoice returns an error - LibraryAssert.ExpectedError('Copilot capability has not been set.'); - end; - - [Test] - procedure AnalyzeInvoiceCapabilityInactive() - var - AzureDI: Codeunit "Azure Document Intelligence"; - begin - // [SCENARIO] AnalyzeInvoice returns an error when capability is not active - - EnvironmentInfoTestLibrary.SetTestabilitySoftwareAsAService(false); - - // [GIVEN] Capability is set - RegisterCapability(Enum::"Copilot Capability"::"Text Capability"); - AzureDI.SetCopilotCapability(Enum::"Copilot Capability"::"Text Capability"); - CopilotTestLibrary.SetCopilotStatus(Enum::"Copilot Capability"::"Text Capability", GetModuleAppId(), Enum::"Copilot Status"::Inactive); - - // [WHEN] AnalyzeInvoice is called - asserterror AzureDI.AnalyzeInvoice('Test'); - - // [THEN] AnalyzeInvoice returns an error - LibraryAssert.ExpectedError('Copilot capability ''Text Capability'' has not been enabled. Please contact your system administrator.'); - end; - - local procedure RegisterCapability(Capability: Enum "Copilot Capability") - var - AzureDI: Codeunit "Azure Document Intelligence"; - CopilotCapability: Codeunit "Copilot Capability"; - begin - if CopilotCapability.IsCapabilityRegistered(Capability) then - exit; - - AzureDI.RegisterCopilotCapability(Capability, Enum::"Copilot Availability"::Preview, ''); - end; - - local procedure GetModuleAppId(): Guid - var - CurrentModuleInfo: ModuleInfo; - begin - NavApp.GetCurrentModuleInfo(CurrentModuleInfo); - exit(CurrentModuleInfo.Id()); - end; -} \ No newline at end of file