diff --git a/src/System Application/App/AI/app.json b/src/System Application/App/AI/app.json
index aee58d9565..a4de64d7de 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": 7778
+ "to": 7780
}
],
"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
new file mode 100644
index 0000000000..00df30b255
--- /dev/null
+++ b/src/System Application/App/AI/src/Azure AI Document Intelligence/ADIModelType.Enum.al
@@ -0,0 +1,29 @@
+// ------------------------------------------------------------------------------------------------
+// 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
new file mode 100644
index 0000000000..cf70ecb106
--- /dev/null
+++ b/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDIImpl.Codeunit.al
@@ -0,0 +1,147 @@
+// ------------------------------------------------------------------------------------------------
+// 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
new file mode 100644
index 0000000000..d9ca35a59b
--- /dev/null
+++ b/src/System Application/App/AI/src/Azure AI Document Intelligence/AzureDocumentIntelligence.Codeunit.al
@@ -0,0 +1,75 @@
+// ------------------------------------------------------------------------------------------------
+// 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 7992af7d3f..21579c9bab 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,6 +270,7 @@ 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 1e03e39e13..d400d41e96 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,11 +8,10 @@ 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"
+codeunit 7772 "Azure OpenAI Impl" implements "AI Service Name"
{
Access = Internal;
InherentEntitlements = X;
@@ -20,8 +19,6 @@ codeunit 7772 "Azure OpenAI Impl"
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";
@@ -35,16 +32,10 @@ codeunit 7772 "Azure OpenAI Impl"
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;
@@ -52,95 +43,27 @@ codeunit 7772 "Azure OpenAI Impl"
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(IsEnabled(Capability, false, CallerModuleInfo));
+ CopilotCapabilityImpl.IsCapabilityEnabled(Capability, CallerModuleInfo);
end;
procedure IsEnabled(Capability: Enum "Copilot Capability"; Silent: Boolean; CallerModuleInfo: ModuleInfo): Boolean
- var
- CopilotNotAvailable: Page "Copilot Not Available";
begin
- 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));
+ CopilotCapabilityImpl.IsCapabilityEnabled(Capability, Silent, CallerModuleInfo);
end;
- [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
- 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";
+ procedure SetCopilotCapability(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo)
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;
+ CopilotCapabilityImpl.SetCopilotCapability(Capability, CallerModuleInfo, Enum::"Azure AI Service Type"::"Azure OpenAI");
end;
procedure IsAuthorizationConfigured(ModelType: Enum "AOAI Model Type"; CallerModule: ModuleInfo): Boolean
@@ -239,11 +162,11 @@ codeunit 7772 "Azure OpenAI Impl"
begin
GuiCheck(TextCompletionsAOAIAuthorization);
- CheckCapabilitySet();
- CheckEnabled(CallerModuleInfo);
+ CopilotCapabilityImpl.CheckCapabilitySet();
+ CopilotCapabilityImpl.CheckEnabled(CallerModuleInfo);
CheckAuthorizationEnabled(TextCompletionsAOAIAuthorization, CallerModuleInfo);
- AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
+ CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
CheckTextCompletionMetaprompt(Metaprompt, CustomDimensions);
UnwrappedPrompt := Metaprompt.Unwrap() + Prompt.Unwrap();
@@ -256,11 +179,11 @@ codeunit 7772 "Azure OpenAI Impl"
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', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, CompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogError('0000KVD', GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, CompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
exit;
end;
- FeatureTelemetry.LogUsage('0000KVL', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogUsage('0000KVL', GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions);
Result := AOAIOperationResponse.GetResult();
end;
@@ -273,21 +196,21 @@ codeunit 7772 "Azure OpenAI Impl"
begin
GuiCheck(EmbeddingsAOAIAuthorization);
- CheckCapabilitySet();
- CheckEnabled(CallerModuleInfo);
+ CopilotCapabilityImpl.CheckCapabilitySet();
+ CopilotCapabilityImpl.CheckEnabled(CallerModuleInfo);
CheckAuthorizationEnabled(EmbeddingsAOAIAuthorization, CallerModuleInfo);
Payload.Add('input', Input.Unwrap());
Payload.WriteTo(PayloadText);
- AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
+ CopilotCapabilityImpl.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', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, EmbeddingsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogError('0000KVE', GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, EmbeddingsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
exit;
end;
- FeatureTelemetry.LogUsage('0000KVM', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogUsage('0000KVM', GetAzureOpenAICategory(), TelemetryGenerateEmbeddingLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions);
exit(ProcessEmbeddingResponse(AOAIOperationResponse));
end;
@@ -327,10 +250,10 @@ codeunit 7772 "Azure OpenAI Impl"
begin
GuiCheck(ChatCompletionsAOAIAuthorization);
- CheckCapabilitySet();
- CheckEnabled(CallerModuleInfo);
+ CopilotCapabilityImpl.CheckCapabilitySet();
+ CopilotCapabilityImpl.CheckEnabled(CallerModuleInfo);
CheckAuthorizationEnabled(ChatCompletionsAOAIAuthorization, CallerModuleInfo);
- AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
+ CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
AOAIChatCompletionParams.AddChatCompletionsParametersToPayload(Payload);
Payload.Add('messages', ChatMessages.AssembleHistory(MetapromptTokenCount, PromptTokenCount));
@@ -356,13 +279,13 @@ codeunit 7772 "Azure OpenAI Impl"
SendTokenCountTelemetry(MetapromptTokenCount, PromptTokenCount, CustomDimensions);
if not SendRequest(Enum::"AOAI Model Type"::"Chat Completions", ChatCompletionsAOAIAuthorization, PayloadText, AOAIOperationResponse, CallerModuleInfo) then begin
- FeatureTelemetry.LogError('0000KVF', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, ChatCompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogError('0000KVF', GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, ChatCompletionsFailedWithCodeErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
exit;
end;
ProcessChatCompletionResponse(ChatMessages, AOAIOperationResponse, CallerModuleInfo);
- FeatureTelemetry.LogUsage('0000KVN', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogUsage('0000KVN', 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);
@@ -418,10 +341,10 @@ codeunit 7772 "Azure OpenAI Impl"
AOAIOperationResponse.AddFunctionResponse(AOAIFunctionResponse);
end;
- AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
+ CopilotCapabilityImpl.AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
foreach AOAIFunctionResponse in AOAIOperationResponse.GetFunctionResponses() do
if not AOAIFunctionResponse.IsSuccess() then
- FeatureTelemetry.LogError('0000MTB', CopilotCapabilityImpl.GetAzureOpenAICategory(), StrSubstNo(TelemetryFunctionCallingFailedErr, AOAIFunctionResponse.GetFunctionName()), AOAIFunctionResponse.GetError(), AOAIFunctionResponse.GetErrorCallstack(), Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogError('0000MTB', 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);
@@ -531,7 +454,7 @@ codeunit 7772 "Azure OpenAI Impl"
ALCopilotAuthorization := ALCopilotAuthorization.Create(AOAIAuthorization.GetEndpoint(), AOAIAuthorization.GetDeployment(), AOAIAuthorization.GetApiKey());
end;
- ALCopilotCapability := ALCopilotCapability.ALCopilotCapability(CallerModuleInfo.Publisher(), CallerModuleInfo.Id(), Format(CallerModuleInfo.AppVersion()), GetCapabilityName());
+ ALCopilotCapability := ALCopilotCapability.ALCopilotCapability(CallerModuleInfo.Publisher(), CallerModuleInfo.Id(), Format(CallerModuleInfo.AppVersion()), CopilotCapabilityImpl.GetCapabilityName());
case ModelType of
Enum::"AOAI Model Type"::"Text Completions":
@@ -556,22 +479,6 @@ codeunit 7772 "Azure OpenAI Impl"
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);
@@ -588,70 +495,12 @@ codeunit 7772 "Azure OpenAI Impl"
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
@@ -692,7 +541,7 @@ codeunit 7772 "Azure OpenAI Impl"
ModuleInfo: ModuleInfo;
begin
if Metaprompt.Unwrap().Trim() = '' then begin
- FeatureTelemetry.LogError('0000LO8', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, EmptyMetapromptErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
+ FeatureTelemetry.LogError('0000LO8', GetAzureOpenAICategory(), TelemetryGenerateTextCompletionLbl, EmptyMetapromptErr, '', Enum::"AL Telemetry Scope"::All, CustomDimensions);
NavApp.GetCurrentModuleInfo(ModuleInfo);
if ModuleInfo.Publisher = 'Microsoft' then
@@ -751,15 +600,39 @@ codeunit 7772 "Azure OpenAI Impl"
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', CopilotCapabilityImpl.GetAzureOpenAICategory());
+ Session.LogMessage('0000MLN', TelemetryEmptyTenantIdErr, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', GetAzureOpenAICategory());
exit(false);
end;
if not AllowlistedTenants.Contains(EntraTenantIdAsText) then
exit(false);
- Session.LogMessage('0000MLE', TelemetryTenantAllowlistedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+ Session.LogMessage('0000MLE', TelemetryTenantAllowlistedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 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
new file mode 100644
index 0000000000..3705990822
--- /dev/null
+++ b/src/System Application/App/AI/src/Copilot/AzureAIServiceType.Enum.al
@@ -0,0 +1,33 @@
+// ------------------------------------------------------------------------------------------------
+// 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 cfda74d386..2ecd9c7ed1 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(CopilotCapabilityImpl.GetAzureOpenAICategory(), false) of
+ case PrivacyNotice.GetPrivacyNoticeApprovalState(AzureOpenAIImpl.GetAzureOpenAICategory(), false) of
Enum::"Privacy Notice Approval State"::Agreed:
AllowDataMovement := true;
Enum::"Privacy Notice Approval State"::Disagreed:
@@ -297,15 +297,16 @@ 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(CopilotCapabilityImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Agreed)
+ PrivacyNotice.SetApprovalState(AzureOpenAIImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Agreed)
else
- PrivacyNotice.SetApprovalState(CopilotCapabilityImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Disagreed);
+ PrivacyNotice.SetApprovalState(AzureOpenAIImpl.GetAzureOpenAICategory(), Enum::"Privacy Notice Approval State"::Disagreed);
CurrPage.GenerallyAvailableCapabilities.Page.SetDataMovement(AllowDataMovement);
CurrPage.PreviewCapabilities.Page.SetDataMovement(AllowDataMovement);
@@ -319,6 +320,7 @@ 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 f322325fb9..f044a41d1f 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"));
+ SourceTableView = where(Availability = const("Early Preview"), "Service Type" = const("Azure AI Service Type"::"Azure OpenAI"));
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 b76b8e4350..241e75d182 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"));
+ SourceTableView = where(Availability = const("Generally Available"), "Service Type" = const("Azure AI Service Type"::"Azure OpenAI"));
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 38e90454e1..5c528a18a6 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));
+ SourceTableView = where(Availability = const(Preview), "Service Type" = const("Azure AI Service Type"::"Azure OpenAI"));
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 366a56b901..1989341ee8 100644
--- a/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al
+++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al
@@ -6,6 +6,7 @@ namespace System.AI;
using System;
using System.Azure.Identity;
+using System.Azure.KeyVault;
using System.Environment;
using System.Environment.Configuration;
using System.Globalization;
@@ -21,11 +22,23 @@ 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;
@@ -38,19 +51,25 @@ 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
@@ -62,9 +81,40 @@ 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
@@ -88,7 +138,6 @@ 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
@@ -110,8 +159,6 @@ 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);
@@ -119,6 +166,11 @@ 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()));
@@ -126,7 +178,6 @@ 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]];
@@ -150,6 +201,140 @@ 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];
@@ -175,11 +360,6 @@ codeunit 7774 "Copilot Capability Impl"
GlobalLanguage(SavedGlobalLanguageId);
end;
- procedure GetAzureOpenAICategory(): Code[50]
- begin
- exit(AzureOpenAiTxt);
- end;
-
procedure GetCopilotCategory(): Code[50]
begin
exit(CopilotCategoryLbl);
@@ -242,15 +422,6 @@ 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 9ee6c06bfb..e86861ff8f 100644
--- a/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al
+++ b/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al
@@ -44,6 +44,10 @@ 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
new file mode 100644
index 0000000000..f7ac245c07
--- /dev/null
+++ b/src/System Application/App/AI/src/Copilot/Interfaces/AIServiceName.Interface.al
@@ -0,0 +1,25 @@
+// ------------------------------------------------------------------------------------------------
+// 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
new file mode 100644
index 0000000000..767f6b6dd5
--- /dev/null
+++ b/src/System Application/Test/AI/src/AzureDITest.Codeunit.al
@@ -0,0 +1,95 @@
+// ------------------------------------------------------------------------------------------------
+// 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