From 7dab30000c9805b2f73d73fb7b46ff3366924e22 Mon Sep 17 00:00:00 2001 From: Dmitry Katson Date: Mon, 28 Apr 2025 14:32:50 +0600 Subject: [PATCH] Enhance No. Series Copilot with semantic search capabilities - Introduced a new codeunit for semantic implementation, enabling semantic vocabulary handling. - Added temporary methods for managing semantic vocabulary and embeddings, with clear TODOs for future cleanup. - Updated existing tools to utilize semantic search where applicable, ensuring a smoother integration once fully implemented. This commit lays the groundwork for enhanced functionality in the No. Series Copilot, focusing on semantic capabilities and future extensibility. --- .../NoSeriesCopilotObjects.permissionset.al | 7 + .../Copilot/NoSeriesCopilotImpl.Codeunit.al | 81 ++++- .../src/Copilot/NoSeriesExt.PageExt.al | 22 +- .../NoSeriesCopSemanticImpl.Codeunit.al | 219 +++++++++++ .../NoSeriesSemanticVocabulary.Table.al | 103 ++++++ .../Tools/NoSeriesCopAddIntent.Codeunit.al | 50 ++- .../Tools/NoSeriesCopChangeIntent.Codeunit.al | 50 ++- .../Tools/NoSeriesCopGenerate.Codeunit.al | 19 +- .../Tools/NoSeriesCopNxtYrIntent.Codeunit.al | 19 +- .../Tools/NoSeriesCopToolsImpl.Codeunit.al | 13 + .../NoSeriesCopilotRegister.Codeunit.al | 17 +- .../NoSeriesCopilotSetup.Page.al | 234 ++++++++++++ .../NoSeriesCopilotSetup.Table.al | 344 ++++++++++++++++++ 13 files changed, 1137 insertions(+), 41 deletions(-) create mode 100644 src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesCopSemanticImpl.Codeunit.al create mode 100644 src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesSemanticVocabulary.Table.al create mode 100644 src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Page.al create mode 100644 src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Table.al diff --git a/src/Business Foundation/App/NoSeriesCopilot/Permissions/NoSeriesCopilotObjects.permissionset.al b/src/Business Foundation/App/NoSeriesCopilot/Permissions/NoSeriesCopilotObjects.permissionset.al index db8cdf26b4..479f3923f0 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/Permissions/NoSeriesCopilotObjects.permissionset.al +++ b/src/Business Foundation/App/NoSeriesCopilot/Permissions/NoSeriesCopilotObjects.permissionset.al @@ -10,6 +10,13 @@ permissionset 330 "No. Series Copilot - Objects" Access = Internal; Assignable = false; Permissions = + // start of + // TODO: Uncomment this line when the semantic codeunit is ready + tabledata "No. Series Copilot Setup" = RIMD, + table "No. Series Copilot Setup" = X, + page "No. Series Copilot Setup" = X, + // end of codeunit "No. Series Copilot Impl." = X, + codeunit "No. Series Cop. Semantic Impl." = X, codeunit "No. Series Text Match Impl." = X; } \ No newline at end of file diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al index 6d523587b7..64af4937bc 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesCopilotImpl.Codeunit.al @@ -145,10 +145,22 @@ codeunit 324 "No. Series Copilot Impl." [NonDebuggable] local procedure GetToolsSelectionSystemPrompt() ToolsSelectionSystemPrompt: SecretText var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of AzureKeyVault: Codeunit "Azure Key Vault"; Telemetry: Codeunit Telemetry; ToolsSelectionPrompt: Text; begin + // start of + // TODO: Remove this once the semantic search is implemented in production. + // This is a temporary solution to get the system prompt. The system prompt should be retrieved from the Azure Key Vault. + if NoSeriesCopilotSetup.Get() then begin + ToolsSelectionSystemPrompt := NoSeriesCopilotSetup.GetToolsSelectionPromptFromIsolatedStorage().Replace(DateSpecificPlaceholderLbl, Format(Today(), 0, 4)); + exit; + end; + // end of if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotToolsSelectionPromptV2', ToolsSelectionPrompt) then begin Telemetry.LogMessage('0000NDY', TelemetryToolsSelectionPromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); Error(ToolLoadingErr); @@ -159,6 +171,10 @@ codeunit 324 "No. Series Copilot Impl." local procedure GenerateNoSeries(SystemPromptTxt: SecretText; InputText: Text): Text var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of AzureOpenAI: Codeunit "Azure OpenAI"; AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params"; AOAIOperationResponse: Codeunit "AOAI Operation Response"; @@ -172,7 +188,13 @@ codeunit 324 "No. Series Copilot Impl." if not AzureOpenAI.IsEnabled(Enum::"Copilot Capability"::"No. Series Copilot") then exit; - AzureOpenAI.SetAuthorization(Enum::"AOAI Model Type"::"Chat Completions", AOAIDeployments.GetGPT4oLatest()); + // start of + // TODO: Remove this once the semantic search is implemented in production. + if NoSeriesCopilotSetup.Get() then + AzureOpenAI.SetAuthorization(Enum::"AOAI Model Type"::"Chat Completions", NoSeriesCopilotSetup.GetEndpoint(), NoSeriesCopilotSetup.GetDeployment(), NoSeriesCopilotSetup.GetSecretKeyFromIsolatedStorage()) + else + // end of + AzureOpenAI.SetAuthorization(Enum::"AOAI Model Type"::"Chat Completions", AOAIDeployments.GetGPT4oLatest()); AzureOpenAI.SetCopilotCapability(Enum::"Copilot Capability"::"No. Series Copilot"); AOAIChatCompletionParams.SetMaxTokens(MaxOutputTokens()); AOAIChatCompletionParams.SetTemperature(0); @@ -192,10 +214,14 @@ codeunit 324 "No. Series Copilot Impl." CompletionAnswerTxt := AOAIChatMessages.GetLastMessage(); // the model can answer to rephrase the question, if the user input is not clear - if AOAIOperationResponse.IsFunctionCall() then - CompletionAnswerTxt := GenerateNoSeriesUsingToolResult(AzureOpenAI, InputText, AOAIOperationResponse, AddNoSeriesIntent.GetExistingNoSeries()) - else - NoSeriesCopilotTelemetry.LogToolNotInvoked(AOAIOperationResponse); + if AOAIOperationResponse.IsFunctionCall() then begin + CompletionAnswerTxt := GenerateNoSeriesUsingToolResult(AzureOpenAI, InputText, AOAIOperationResponse, AddNoSeriesIntent.GetExistingNoSeries()); + // start of + // TODO: If semantic vocabulary is not required, we can remove the code. + AddNoSeriesIntent.UpdateSemanticVocabulary(); + ChangeNoSeriesIntent.UpdateSemanticVocabulary(); + // end of + end; exit(CompletionAnswerTxt); end; @@ -237,7 +263,7 @@ codeunit 324 "No. Series Copilot Impl." AOAIChatMessages.SetToolChoice(NoSeriesGenerateTool.GetDefaultToolChoice()); // call the API again to get the final response from the model - if not GenerateAndReviewToolCompletionWithRetry(AzureOpenAI, AOAIChatMessages, AOAIChatCompletionParams, GeneratedNoSeriesArray, GetExpectedNoSeriesCount(ToolResponse, SystemPrompt)) then + if not GenerateAndReviewToolCompletionWithRetry(AzureOpenAI, AOAIChatMessages, AOAIChatCompletionParams, GeneratedNoSeriesArray, AOAIFunctionResponse.GetFunctionName(), GetExpectedNoSeriesCount(ToolResponse, SystemPrompt)) then Error(GetLastErrorText()); FinalResults.Add(GeneratedNoSeriesArray); @@ -255,7 +281,7 @@ codeunit 324 "No. Series Copilot Impl." ToolResponse.Get(Message, ExpectedNoSeriesCount); end; - local procedure GenerateAndReviewToolCompletionWithRetry(var AzureOpenAI: Codeunit "Azure OpenAI"; var AOAIChatMessages: Codeunit "AOAI Chat Messages"; var AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params"; var GeneratedNoSeriesArrayText: Text; ExpectedNoSeriesCount: Integer): Boolean + local procedure GenerateAndReviewToolCompletionWithRetry(var AzureOpenAI: Codeunit "Azure OpenAI"; var AOAIChatMessages: Codeunit "AOAI Chat Messages"; var AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params"; var GeneratedNoSeriesArrayText: Text; Intent: Text; ExpectedNoSeriesCount: Integer): Boolean var AOAIOperationResponse: Codeunit "AOAI Operation Response"; AOAIFunctionResponse: Codeunit "AOAI Function Response"; @@ -279,7 +305,7 @@ codeunit 324 "No. Series Copilot Impl." Error(AOAIFunctionResponse.GetError()); GeneratedNoSeriesArrayText := AOAIFunctionResponse.GetResult(); - if CheckIfValidResult(GeneratedNoSeriesArrayText, AOAIFunctionResponse.GetFunctionName(), ExpectedNoSeriesCount) then begin + if CheckIfValidResult(GeneratedNoSeriesArrayText, Intent, ExpectedNoSeriesCount) then begin NoSeriesCopilotTelemetry.LogGenerationCompletion(ReadGeneratedNumberSeriesJArray(GeneratedNoSeriesArrayText).Count, ExpectedNoSeriesCount, Attempt); exit(true); end; @@ -291,14 +317,14 @@ codeunit 324 "No. Series Copilot Impl." exit(false); end; - local procedure CheckIfValidResult(GeneratedNoSeriesArrayText: Text; FunctionName: Text; ExpectedNoSeriesCount: Integer): Boolean + local procedure CheckIfValidResult(GeneratedNoSeriesArrayText: Text; Intent: Text; ExpectedNoSeriesCount: Integer): Boolean var AddNoSeriesIntent: Codeunit "No. Series Cop. Add Intent"; begin if not CheckIfCompletionMeetAllRequirements(GeneratedNoSeriesArrayText) then exit(false); - if FunctionName = AddNoSeriesIntent.GetName() then + if Intent = AddNoSeriesIntent.GetName() then exit(CheckIfExpectedNoSeriesCount(GeneratedNoSeriesArrayText, ExpectedNoSeriesCount)); exit(true); @@ -454,6 +480,7 @@ codeunit 324 "No. Series Copilot Impl." begin ReadGeneratedNumberSeriesJArray(Completion).WriteTo(NoSeriesArrText); ReassembleDuplicates(NoSeriesArrText); + ExcludeExistingNoSeriesIfNewWhereGenerated(NoSeriesArrText); Json.InitializeCollection(NoSeriesArrText); @@ -494,6 +521,40 @@ codeunit 324 "No. Series Copilot Impl." NoSeriesCodes.Add(NoSeriesCode); end; + local procedure ExcludeExistingNoSeriesIfNewWhereGenerated(var NoSeriesArrText: Text) + var + Json: Codeunit Json; + i: Integer; + NoSeriesArr: JsonArray; + NoSeriesObj: Text; + begin + if not CheckIfGeneratedNoSeriesExists(NoSeriesArrText) then + exit; + + Json.InitializeCollection(NoSeriesArrText); + for i := 0 to Json.GetCollectionCount() - 1 do begin + Json.GetObjectFromCollectionByIndex(i, NoSeriesObj); + if CheckIfNumberSeriesIsGenerated(Json) then + NoSeriesArr.Add(Json.GetObject()); + end; + NoSeriesArr.WriteTo(NoSeriesArrText); + end; + + local procedure CheckIfGeneratedNoSeriesExists(NoSeriesArrText: Text): Boolean + var + Json: Codeunit Json; + i: Integer; + NoSeriesObj: Text; + begin + Json.InitializeCollection(NoSeriesArrText); + for i := 0 to Json.GetCollectionCount() - 1 do begin + Json.GetObjectFromCollectionByIndex(i, NoSeriesObj); + if CheckIfNumberSeriesIsGenerated(Json) then + exit(true); + end; + exit(false); + end; + local procedure InsertGeneratedNoSeries(var GeneratedNoSeries: Record "No. Series Generation Detail"; NoSeriesObj: Text; GenerationNo: Integer) var Json: Codeunit Json; diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesExt.PageExt.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesExt.PageExt.al index aa2b4b808d..1f3bcf3370 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesExt.PageExt.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/NoSeriesExt.PageExt.al @@ -18,7 +18,6 @@ pageextension 324 "No. Series Ext." extends "No. Series" Image = Sparkle; ApplicationArea = All; Visible = CopilotActionsVisible; - Enabled = CopilotActionsVisible; trigger OnAction() var @@ -28,6 +27,27 @@ pageextension 324 "No. Series Ext." extends "No. Series" end; } } + // start of + // TODO: Remove this action once the semantic search is implemented in production. + addfirst(Processing) + { + action("Generate With Copilot Processing") + { + Caption = 'Generate'; + ToolTip = 'Generate No. Series using Copilot'; + Image = Sparkle; + ApplicationArea = All; + Visible = CopilotActionsVisible; + + trigger OnAction() + var + NoSeriesCopilotImpl: Codeunit "No. Series Copilot Impl."; + begin + NoSeriesCopilotImpl.GetNoSeriesSuggestions(); + end; + } + } + // end of } trigger OnOpenPage() diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesCopSemanticImpl.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesCopSemanticImpl.Codeunit.al new file mode 100644 index 0000000000..88ee2c2eed --- /dev/null +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesCopSemanticImpl.Codeunit.al @@ -0,0 +1,219 @@ +// start of +// TODO: Refactor the code below when embedding is supported by the platform + +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.Foundation.NoSeries; +using System.AI; +using System.Utilities; + +codeunit 348 "No. Series Cop. Semantic Impl." +{ + Access = Internal; + InherentPermissions = X; + InherentEntitlements = X; + + var + TempNoSeriesSemanticVocabularyBuffer: Record "No. Series Semantic Vocabulary" temporary; + + procedure IsRelevant(FirstString: Text; SecondString: Text): Boolean + var + Score: Decimal; + begin + Score := Round(CalculateSemanticNearness(FirstString, SecondString), 0.01, '>'); + exit(Score >= RequiredNearness()); + end; + + local procedure CalculateSemanticNearness(FirstString: Text; SecondString: Text): Decimal + var + FirstStringVector: List of [Decimal]; + SecondStringVector: List of [Decimal]; + begin + if (FirstString = '') or (SecondString = '') then + exit(0); + + FirstStringVector := GetVectorFromText(FirstString); + SecondStringVector := GetVectorFromText(SecondString); + exit(CalculateCosineSimilarityFromNormalizedVectors(FirstStringVector, SecondStringVector)); + end; + + local procedure GetVectorFromText(Input: Text): List of [Decimal] + var + NoSeriesSemanticVocabulary: Record "No. Series Semantic Vocabulary"; + VectorsArray: JsonArray; + begin + Input := PrepareTextForEmbeddings(Input); + + if GetEmbeddingsFromVocabulary(Input, VectorsArray, NoSeriesSemanticVocabulary) then + exit(ConvertJsonArrayToListOfDecimals(VectorsArray)); + + if GetEmbeddingsFromVocabulary(Input, VectorsArray, TempNoSeriesSemanticVocabularyBuffer) then + exit(ConvertJsonArrayToListOfDecimals(VectorsArray)); + + VectorsArray := GetAzureOpenAIEmbeddings(Input); + // start of + // TODO: Remove this if the semantic vocabulary is not required + SaveEmbeddingsToVocabulary(CopyStr(Input, 1, 2048), VectorsArray, TempNoSeriesSemanticVocabularyBuffer); + // end of + exit(ConvertJsonArrayToListOfDecimals(VectorsArray)); + end; + + // start of + // TODO: Remove this procedure if semantic vocabulary is not required + local procedure GetEmbeddingsFromVocabulary(Input: Text; var EmbeddingsArray: JsonArray; var NoSeriesSemanticVocabulary: Record "No. Series Semantic Vocabulary"): Boolean + begin + NoSeriesSemanticVocabulary.SetCurrentKey(Payload); + NoSeriesSemanticVocabulary.SetLoadFields(Payload); + NoSeriesSemanticVocabulary.SetRange(Payload, Input); + if not NoSeriesSemanticVocabulary.FindFirst() then + exit(false); + + EmbeddingsArray.ReadFrom(NoSeriesSemanticVocabulary.LoadVectorText()); + exit(true); + end; + // end of + + local procedure PrepareTextForEmbeddings(Input: Text): Text + begin + exit(Input.ToLower().TrimStart().TrimEnd()); + end; + + // start of + // TODO: Remove this procedure if semantic vocabulary is not required + local procedure SaveEmbeddingsToVocabulary(Input: Text[2048]; EmbeddingsArray: JsonArray; var NoSeriesSemanticVocabulary: Record "No. Series Semantic Vocabulary") + var + VectorText: Text; + begin + EmbeddingsArray.WriteTo(VectorText); + + NoSeriesSemanticVocabulary.InsertRecord(Input); + NoSeriesSemanticVocabulary.SaveVectorText(VectorText); + end; + // end of + + // start of + // TODO: Remove this procedure if semantic vocabulary is not required + internal procedure UpdateSemanticVocabulary() + var + NoSeriesSemanticVocabulary: Record "No. Series Semantic Vocabulary"; + begin + TempNoSeriesSemanticVocabularyBuffer.Reset(); + if TempNoSeriesSemanticVocabularyBuffer.FindSet() then + repeat + NoSeriesSemanticVocabulary.InsertRecord(TempNoSeriesSemanticVocabularyBuffer.Payload); + NoSeriesSemanticVocabulary.SaveVectorText(TempNoSeriesSemanticVocabularyBuffer.LoadVectorText()); + until TempNoSeriesSemanticVocabularyBuffer.Next() = 0; + end; + // end of + local procedure GetAzureOpenAIEmbeddings(Input: Text): JsonArray + var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of + AzureOpenAI: Codeunit "Azure OpenAI"; + AOAIOperationResponse: Codeunit "AOAI Operation Response"; + // start of + // TODO: Uncomment this line when Microsoft Deployment is used or when embedding is supported by the platform + // AOAIDeployments: Codeunit "AOAI Deployments"; + // end of + begin + if not AzureOpenAI.IsEnabled(Enum::"Copilot Capability"::"No. Series Copilot") then + exit; + + // start of + // TODO: Remove this once the semantic search is implemented in production. + if NoSeriesCopilotSetup.Get() then + AzureOpenAI.SetAuthorization(Enum::"AOAI Model Type"::Embeddings, NoSeriesCopilotSetup.GetEndpoint(), NoSeriesCopilotSetup.GetEmbeddingsDeployment(), NoSeriesCopilotSetup.GetSecretKeyFromIsolatedStorage()); + // end of + // start of + // TODO: Add text-embedding-ada-002 deployment and uncomment this line when Microsoft Deployment is used or when embedding is supported by the platform + // AzureOpenAI.SetAuthorization(Enum::"AOAI Model Type"::Embeddings, AOAIDeployments.GetEmbeddingAda002()); + // end of + AzureOpenAI.SetCopilotCapability(Enum::"Copilot Capability"::"No. Series Copilot"); + AzureOpenAI.GenerateEmbeddings(Input, AOAIOperationResponse); + + if AOAIOperationResponse.IsSuccess() then + exit(GetVectorArray(AOAIOperationResponse.GetResult())) + else + Error(AOAIOperationResponse.GetError()); + end; + + // + // Calculates the cosine similarity between two vectors. + // + internal procedure CalculateCosineSimilarity(FirstVector: List of [Decimal]; SecondVector: List of [Decimal]): Decimal + var + Math: Codeunit Math; + DotProduct: Decimal; + MagnitudeFirstVector: Decimal; + MagnitudeSecondVector: Decimal; + i: Integer; + begin + DotProduct := 0; + MagnitudeFirstVector := 0; + MagnitudeSecondVector := 0; + + for i := 1 to FirstVector.Count() do begin + DotProduct += FirstVector.Get(i) * SecondVector.Get(i); + MagnitudeFirstVector += Math.Pow(FirstVector.Get(i), 2); + MagnitudeSecondVector += Math.Pow(SecondVector.Get(i), 2); + end; + + MagnitudeFirstVector := Math.Sqrt(MagnitudeFirstVector); + MagnitudeSecondVector := Math.Sqrt(MagnitudeSecondVector); + + if (MagnitudeFirstVector = 0) or (MagnitudeSecondVector = 0) then + exit(0); + + exit(DotProduct / (MagnitudeFirstVector * MagnitudeSecondVector)); + end; + + // + // Calculates the cosine similarity between two normalized vectors. + // + internal procedure CalculateCosineSimilarityFromNormalizedVectors(FirstVector: List of [Decimal]; SecondVector: List of [Decimal]): Decimal + var + DotProduct: Decimal; + i: Integer; + begin + DotProduct := 0; + + for i := 1 to FirstVector.Count() do + DotProduct += FirstVector.Get(i) * SecondVector.Get(i); + + exit(DotProduct); + end; + + local procedure GetVectorArray(Response: Text): JsonArray + var + Vector: JsonObject; + Tok: JsonToken; + begin + Vector.ReadFrom(Response); + Vector.Get('vector', Tok); + exit(Tok.AsArray()); + end; + + local procedure ConvertJsonArrayToListOfDecimals(EmbeddingArray: JsonArray) Result: List of [Decimal] + var + i: Integer; + EmbeddingValue: JsonToken; + begin + for i := 0 to EmbeddingArray.Count() - 1 do begin + EmbeddingArray.Get(i, EmbeddingValue); + Result.Add(EmbeddingValue.AsValue().AsDecimal()); + end; + end; + + local procedure RequiredNearness(): Decimal + begin + exit(0.8) + end; + +} + +// end of \ No newline at end of file diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesSemanticVocabulary.Table.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesSemanticVocabulary.Table.al new file mode 100644 index 0000000000..77f212aa4f --- /dev/null +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Semantic/NoSeriesSemanticVocabulary.Table.al @@ -0,0 +1,103 @@ +// TODO: Refactor the code below when embedding is supported by the platform or remove completely if semantic vocabulary is not required + +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace Microsoft.Foundation.NoSeries; +using System.Text; + +table 393 "No. Series Semantic Vocabulary" +{ + Caption = 'No. Series Semantic Vocabulary'; + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + fields + { + field(1; "Entry No."; Integer) + { + DataClassification = SystemMetadata; + Caption = 'Entry No.'; + } + field(2; Payload; Text[2048]) + { + DataClassification = CustomerContent; + Caption = 'Payload'; + } + field(12; Vector; Blob) + { + DataClassification = SystemMetadata; + Caption = 'Vector'; + Compressed = false; + } + } + + keys + { + key(PK; "Entry No.") + { + Clustered = true; + } + key(Payload; Payload) + { + + } + } + + var + ModifyingTheNoSeriesSemanticVocabularyTableIsNotAllowedErr: Label 'Modifying the No. Series Semantic Vocabulary table is not allowed.'; + + trigger OnModify() + begin + Error(ModifyingTheNoSeriesSemanticVocabularyTableIsNotAllowedErr); + end; + + internal procedure InsertRecord(PayloadText: Text[2048]) + var + NextEntryNo: Integer; + begin + NextEntryNo := GetNextNo(); + + Rec.Init(); + Rec."Entry No." := NextEntryNo; + Rec.Payload := PayloadText; + Rec.Insert(false); + end; + + local procedure GetNextNo(): Integer + begin + Rec.Reset(); + if Rec.FindLast() then + exit(Rec."Entry No." + 1); + exit(1); + end; + + internal procedure SaveVectorText(VectorText: Text) + var + Base64Convert: Codeunit "Base64 Convert"; + OutStream: OutStream; + ConvertedText: Text; + begin + ConvertedText := Base64Convert.ToBase64(VectorText); + Clear(Rec.Vector); + Rec.Vector.CreateOutStream(OutStream); + OutStream.WriteText(ConvertedText); + Rec.Modify(); + end; + + internal procedure LoadVectorText() VectorText: Text + var + Base64Convert: Codeunit "Base64 Convert"; + InStream: InStream; + ConvertedText: Text; + begin + Rec.CalcFields(Vector); + Rec.Vector.CreateInStream(InStream); + InStream.ReadText(ConvertedText); + VectorText := Base64Convert.FromBase64(ConvertedText); + end; +} +// end of \ No newline at end of file diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al index 0ff0574629..a6068e1ade 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopAddIntent.Codeunit.al @@ -28,7 +28,7 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function" NumberOfAddedTablesPlaceholderLbl: Label '{number_of_tables}', Locked = true; TelemetryTool1PromptRetrievalErr: Label 'Unable to retrieve the prompt for No. Series Copilot Tool 1 from Azure Key Vault.', Locked = true; TelemetryTool1DefinitionRetrievalErr: Label 'Unable to retrieve the definition for No. Series Copilot Tool 1 from Azure Key Vault.', Locked = true; - ToolProgressDialogTextLbl: Label 'Searching for tables with number series related to your query'; + ToolProgressDialogTextLbl: Label 'Searching for tables with number series semantically related to your query'; ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 1. Please try again later.'; ExistingNoSeriesMessageLbl: Label 'Number series already configured. If you wish to modify the existing series, please use the `Modify number series` prompt.'; @@ -122,6 +122,14 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function" until Field.Next() = 0; end; + // start of + //TODO: Remove this procedure if the semantic vocabulary is not required + procedure UpdateSemanticVocabulary() + begin + ToolsImpl.UpdateSemanticVocabulary(); + end; + // end of + local procedure ListAllTablesWithNumberSeries(var TempSetupTable: Record "Table Metadata" temporary; var TempNoSeriesField: Record "Field" temporary) var TempTableMetadata: Record "Table Metadata" temporary; @@ -206,20 +214,44 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function" [NonDebuggable] local procedure GetToolPrompt() Prompt: Text + var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of begin - if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool1Prompt', Prompt) then begin - Telemetry.LogMessage('0000ND4', TelemetryTool1PromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); - Error(ToolLoadingErr); - end; + // start of + // TODO: Remove this once the semantic search is implemented in production. + // This is a temporary solution to get the tool prompt. The tool should be retrieved from the Azure Key Vault. + if NoSeriesCopilotSetup.Get() then + exit(NoSeriesCopilotSetup.GetTool1PromptFromIsolatedStorage()) + else + // end of + if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool1Prompt', Prompt) then begin + Telemetry.LogMessage('0000ND4', TelemetryTool1PromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); + Error(ToolLoadingErr); + end; end; [NonDebuggable] local procedure GetToolDefinition() Definition: Text + var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of begin - if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool1Definition', Definition) then begin - Telemetry.LogMessage('0000ND5', TelemetryTool1DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); - Error(ToolLoadingErr); - end; + // start of + // TODO: Remove this once the semantic search is implemented in production. + // This is a temporary solution to get the tool definition. The tool should be retrieved from the Azure Key Vault. + if NoSeriesCopilotSetup.Get() then + exit(NoSeriesCopilotSetup.GetTool1DefinitionFromIsolatedStorage()) + else + // end of + if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool1Definition', Definition) then begin + Telemetry.LogMessage('0000ND5', TelemetryTool1DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); + Error(ToolLoadingErr); + end; end; } \ No newline at end of file diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopChangeIntent.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopChangeIntent.Codeunit.al index 73be7c0b0e..b53e714b0a 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopChangeIntent.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopChangeIntent.Codeunit.al @@ -32,7 +32,7 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function" NumberOfAddedTablesPlaceholderLbl: Label '{number_of_tables}', Locked = true; TelemetryTool2PromptRetrievalErr: Label 'Unable to retrieve the prompt for No. Series Copilot Tool 2 from Azure Key Vault.', Locked = true; TelemetryTool2DefinitionRetrievalErr: Label 'Unable to retrieve the definition for No. Series Copilot Tool 2 from Azure Key Vault.', Locked = true; - ToolProgressDialogTextLbl: Label 'Searching for tables with number series related to your query'; + ToolProgressDialogTextLbl: Label 'Searching for tables with number series semantically related to your query'; ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 2. Please try again later.'; procedure GetName(): Text @@ -148,6 +148,14 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function" until Field.Next() = 0; end; + // start of + //TODO: Remove this procedure if the semantic vocabulary is not required + procedure UpdateSemanticVocabulary() + begin + ToolsImpl.UpdateSemanticVocabulary(); + end; + // end of + local procedure AddChangeNoSeriesFieldToTablesList(var TempSetupTable: Record "Table Metadata" temporary; var TempNoSeriesField: Record "Field" temporary; var ExistingNoSeriesToChangeList: List of [Text]; TempTableMetadata: Record "Table Metadata" temporary; Field: Record "Field") var NoSeries: Record "No. Series"; @@ -177,19 +185,43 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function" [NonDebuggable] local procedure GetToolPrompt() Prompt: Text + var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of begin - if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool2Prompt', Prompt) then begin - Telemetry.LogMessage('0000ND6', TelemetryTool2PromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); - Error(ToolLoadingErr); - end; + // start of + // TODO: Remove this once the semantic search is implemented in production. + // This is a temporary solution to get the tool definition. The tool should be retrieved from the Azure Key Vault. + if NoSeriesCopilotSetup.Get() then + exit(NoSeriesCopilotSetup.GetTool2PromptFromIsolatedStorage()) + else + // end of + if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool2Prompt', Prompt) then begin + Telemetry.LogMessage('0000ND6', TelemetryTool2PromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); + Error(ToolLoadingErr); + end; end; [NonDebuggable] local procedure GetTool2Definition() Definition: Text + var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of begin - if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool2Definition', Definition) then begin - Telemetry.LogMessage('0000ND7', TelemetryTool2DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); - Error(ToolLoadingErr); - end; + // start of + // TODO: Remove this once the semantic search is implemented in production. + // This is a temporary solution to get the tool definition. The tool should be retrieved from the Azure Key Vault. + if NoSeriesCopilotSetup.Get() then + exit(NoSeriesCopilotSetup.GetTool2DefinitionFromIsolatedStorage()) + else + // end of + if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool2Definition', Definition) then begin + Telemetry.LogMessage('0000ND7', TelemetryTool2DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); + Error(ToolLoadingErr); + end; end; } \ No newline at end of file diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopGenerate.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopGenerate.Codeunit.al index d0b4438fd1..9d400c81b2 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopGenerate.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopGenerate.Codeunit.al @@ -55,12 +55,23 @@ codeunit 339 "No. Series Cop. Generate" implements "AOAI Function" [NonDebuggable] local procedure GetTool4Definition() Definition: Text var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of AzureKeyVault: Codeunit "Azure Key Vault"; begin - if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool4Definition', Definition) then begin - Telemetry.LogMessage('0000ND8', TelemetryTool4DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); - Error(ToolLoadingErr); - end; + // start of + // TODO: Remove this once the semantic search is implemented in production. + // This is a temporary solution to get the tool definition. The tool should be retrieved from the Azure Key Vault. + if NoSeriesCopilotSetup.Get() then + exit(NoSeriesCopilotSetup.GetTool4DefinitionFromIsolatedStorage()) + else + // end of + if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool4Definition', Definition) then begin + Telemetry.LogMessage('0000ND8', TelemetryTool4DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); + Error(ToolLoadingErr); + end; end; [TryFunction] diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopNxtYrIntent.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopNxtYrIntent.Codeunit.al index 9babc47e9f..9b1eaf7ad7 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopNxtYrIntent.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopNxtYrIntent.Codeunit.al @@ -43,11 +43,22 @@ codeunit 349 "No. Series Cop. Nxt Yr. Intent" implements "AOAI Function" [NonDebuggable] local procedure GetTool3Definition() Definition: Text var + // start of + // TODO: Remove this once the semantic search is implemented in production. + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + // end of AzureKeyVault: Codeunit "Azure Key Vault"; begin - if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool3DefinitionV2', Definition) then begin - Telemetry.LogMessage('0000ND9', TelemetryTool3DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); - Error(ToolLoadingErr); - end; + // start of + // TODO: Remove this once the semantic search is implemented in production. + // This is a temporary solution to get the tool definition. The tool should be retrieved from the Azure Key Vault. + if NoSeriesCopilotSetup.Get() then + exit(NoSeriesCopilotSetup.GetTool3DefinitionFromIsolatedStorage()) + else + // end of + if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool3DefinitionV2', Definition) then begin + Telemetry.LogMessage('0000ND9', TelemetryTool3DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata); + Error(ToolLoadingErr); + end; end; } \ No newline at end of file diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al index c132c22a73..ec49a1d3af 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Tools/NoSeriesCopToolsImpl.Codeunit.al @@ -14,6 +14,7 @@ codeunit 336 "No. Series Cop. Tools Impl." Access = Internal; var + SemanticImpl: Codeunit "No. Series Cop. Semantic Impl."; PrefixLbl: Label 'for '; procedure GetUserSpecifiedOrExistingNumberPatternsGuidelines(var Arguments: JsonObject; var CustomPatternsPromptList: List of [Text]; var ExistingNoSeriesToChangeList: List of [Text]; UpdateForNextYear: Boolean) @@ -236,10 +237,22 @@ codeunit 336 "No. Series Cop. Tools Impl." if TextMatchImpl.IsRelevant(String1, String2) then exit(true); + + if SemanticImpl.IsRelevant(String1, String2) then + exit(true); + end; exit(false); end; + // start of + //TODO: Remove this procedure if the semantic vocabulary is not required + procedure UpdateSemanticVocabulary() + begin + SemanticImpl.UpdateSemanticVocabulary(); + end; + // end of + procedure GenerateChunkedTablesListInYamlFormat(var TablesYamlList: List of [Text]; var TempSetupTable: Record "Table Metadata" temporary; var TempNoSeriesField: Record "Field" temporary; var NumberOfAddedTables: Integer) begin Clear(TablesYamlList); diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Register/NoSeriesCopilotRegister.Codeunit.al b/src/Business Foundation/App/NoSeriesCopilot/src/Register/NoSeriesCopilotRegister.Codeunit.al index cbab6bd7b7..99a40dbb12 100644 --- a/src/Business Foundation/App/NoSeriesCopilot/src/Register/NoSeriesCopilotRegister.Codeunit.al +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Register/NoSeriesCopilotRegister.Codeunit.al @@ -7,7 +7,10 @@ namespace Microsoft.Foundation.NoSeries; using System.AI; using System.Upgrade; -using System.Environment; +// start of +// TODO: Uncomment this once the semantic search is implemented in production. +// using System.Environment; +// end of codeunit 327 "No. Series Copilot Register" { @@ -24,12 +27,18 @@ codeunit 327 "No. Series Copilot Register" procedure RegisterCapability() var CopilotCapability: Codeunit "Copilot Capability"; - EnvironmentInformation: Codeunit "Environment Information"; + // start of + // TODO: Uncomment this once the semantic search is implemented in production. + // EnvironmentInformation: Codeunit "Environment Information"; + // end of UpgradeTag: Codeunit "Upgrade Tag"; NoSeriesCopilotUpgradeTags: Codeunit "No. Series Copilot Upgr. Tags"; begin - if not EnvironmentInformation.IsSaaSInfrastructure() then - exit; + // start of + // TODO: Uncomment this once the semantic search is implemented in production. + // if not EnvironmentInformation.IsSaaSInfrastructure() then + // exit; + // end of if UpgradeTag.HasUpgradeTag(NoSeriesCopilotUpgradeTags.GetImplementationUpgradeTag()) then exit; diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Page.al b/src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Page.al new file mode 100644 index 0000000000..518cd1355a --- /dev/null +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Page.al @@ -0,0 +1,234 @@ +// TODO: Remove this page once the number series copilot is fully integrated with the system. + +/// +/// This page is used to setup the Copilot No. Series. The page is used to store the secret key and the endpoint of the Azure OpenAI service. +/// + +namespace Microsoft.Foundation.NoSeries; + +page 9245 "No. Series Copilot Setup" +{ + + Caption = 'No. Series with Copilot Setup'; + PageType = Card; + SourceTable = "No. Series Copilot Setup"; + InsertAllowed = false; + DeleteAllowed = false; + ApplicationArea = All; + UsageCategory = Administration; + AdditionalSearchTerms = 'no series copilot, number series copilot'; + InherentPermissions = X; + InherentEntitlements = X; + + + layout + { + area(content) + { + group(General) + { + field(Endpoint; Rec.Endpoint) + { + ApplicationArea = All; + ToolTip = 'Specifies the value of the Endpoint field.'; + } + field(Deployment; Rec.Deployment) + { + ApplicationArea = All; + ToolTip = 'Specifies the value of the Deployment field.'; + } + } + group(Embedding) + { + field("Embeddings Deployment"; Rec."Embeddings Deployment") + { + Caption = 'Deployment'; + ApplicationArea = All; + ToolTip = 'Specifies the value of the Embeddings Deployment field.'; + } + } + group(Secrets) + { + field(SecretKey; SecretKey) + { + ApplicationArea = Basic, Suite; + Caption = 'Secret Key'; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + ToolTip = 'Specifies the value of the Secret Key field.'; + trigger OnValidate() + begin + Rec.SetSecretKeyToIsolatedStorage(SecretKey); + end; + } + } + + group(Tools) + { + group(ToolsGeneral) + { + ShowCaption = false; + + field(ToolsSelectionPrompt; ToolsSelectionPrompt) + { + ApplicationArea = Basic, Suite; + Caption = 'Tools Selection Prompt'; + Editable = false; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + MultiLine = true; + ToolTip = 'Specifies the value of the Tools Selection System Prompt field.'; + trigger OnAssistEdit() + begin + Rec.ImportFromTextFile(ToolsSelectionPrompt); + Rec.SetToolsSelectionPromptToIsolatedStorage(ToolsSelectionPrompt); + end; + } + } + group(Tool1) + { + field(Tool1Prompt; Tool1Prompt) + { + ApplicationArea = All; + Caption = 'Tool 1 Prompt'; + Editable = false; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + MultiLine = true; + ToolTip = 'Specifies the value of the Tool 1 Prompt field.'; + trigger OnAssistEdit() + begin + Rec.ImportFromTextFile(Tool1Prompt); + Rec.SetTool1PromptToIsolatedStorage(Tool1Prompt); + end; + } + + field(Tool1Definition; Tool1Definition) + { + ApplicationArea = Basic, Suite; + Caption = 'Tool 1 Definition'; + Editable = false; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + MultiLine = true; + ToolTip = 'Specifies the value of the Tool 1 Definition field.'; + trigger OnAssistEdit() + begin + Rec.ImportFromTextFile(Tool1Definition); + Rec.SetTool1DefinitionToIsolatedStorage(Tool1Definition); + end; + } + } + group(Tool2) + { + field(Tool2Prompt; Tool2Prompt) + { + ApplicationArea = All; + Caption = 'Tool 2 Prompt'; + Editable = false; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + MultiLine = true; + ToolTip = 'Specifies the value of the Tool 2 Prompt field.'; + trigger OnAssistEdit() + begin + Rec.ImportFromTextFile(Tool2Prompt); + Rec.SetTool2PromptToIsolatedStorage(Tool2Prompt); + end; + } + + field(Tool2Definition; Tool2Definition) + { + ApplicationArea = Basic, Suite; + Caption = 'Tool 2 Definition'; + Editable = false; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + MultiLine = true; + ToolTip = 'Specifies the value of the Tool 2 Definition field.'; + trigger OnAssistEdit() + begin + Rec.ImportFromTextFile(Tool2Definition); + Rec.SetTool2DefinitionToIsolatedStorage(Tool2Definition); + end; + } + } + group(Tool3) + { + field(Tool3Definition; Tool3Definition) + { + ApplicationArea = Basic, Suite; + Caption = 'Tool 3 Definition'; + Editable = false; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + MultiLine = true; + ToolTip = 'Specifies the value of the Tool 3 Definition field.'; + trigger OnAssistEdit() + begin + Rec.ImportFromTextFile(Tool3Definition); + Rec.SetTool3DefinitionToIsolatedStorage(Tool3Definition); + end; + } + } + group(Tool4) + { + field(Tool4Definition; Tool4Definition) + { + ApplicationArea = Basic, Suite; + Caption = 'Tool 4 Definition'; + Editable = false; + NotBlank = true; + ShowMandatory = true; + ExtendedDatatype = Masked; + MultiLine = true; + ToolTip = 'Specifies the value of the Tool 4 Definition field.'; + trigger OnAssistEdit() + begin + Rec.ImportFromTextFile(Tool4Definition); + Rec.SetTool4DefinitionToIsolatedStorage(Tool4Definition); + end; + } + } + } + } + } + + var + [NonDebuggable] + SecretKey: Text; + ToolsSelectionPrompt: Text; + Tool1Prompt: Text; + Tool1Definition: Text; + Tool2Prompt: Text; + Tool2Definition: Text; + Tool3Definition: Text; + Tool4Definition: Text; + + trigger OnOpenPage() + begin + if not Rec.Get() then + Rec.Insert(); + end; + + trigger OnAfterGetCurrRecord() + begin + SecretKey := Rec.GetSecretKeyFromIsolatedStorage(); + ToolsSelectionPrompt := Rec.GetToolsSelectionPromptFromIsolatedStorage(); + Tool1Prompt := Rec.GetTool1PromptFromIsolatedStorage(); + Tool1Definition := Rec.GetTool1DefinitionFromIsolatedStorage(); + Tool2Prompt := Rec.GetTool2PromptFromIsolatedStorage(); + Tool2Definition := Rec.GetTool2DefinitionFromIsolatedStorage(); + Tool3Definition := Rec.GetTool3DefinitionFromIsolatedStorage(); + Tool4Definition := Rec.GetTool4DefinitionFromIsolatedStorage(); + end; + +} +// end of \ No newline at end of file diff --git a/src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Table.al b/src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Table.al new file mode 100644 index 0000000000..ece0a20af9 --- /dev/null +++ b/src/Business Foundation/App/NoSeriesCopilot/src/Setup [DRAFT]/NoSeriesCopilotSetup.Table.al @@ -0,0 +1,344 @@ +// TODO: Remove this table once the number series copilot is fully integrated with the system. +/// +/// This is temporary table to store the endpoint and secret key for the number series copilot. +/// Should be removed once the number series copilot is fully integrated with the system. +/// Shoulbe replaced with the Azure Key Vault storage. +/// + +namespace Microsoft.Foundation.NoSeries; + +table 9200 "No. Series Copilot Setup" +{ + Description = 'Number Series Copilot Setup'; + InherentPermissions = X; + InherentEntitlements = X; + + fields + { + field(1; "Code"; Code[20]) + { + DataClassification = CustomerContent; + Caption = 'Code'; + + } + + field(2; Endpoint; Text[250]) + { + DataClassification = CustomerContent; + Caption = 'Endpoint'; + } + + field(3; Deployment; Text[250]) + { + DataClassification = CustomerContent; + Caption = 'Deployment'; + } + + field(4; "Secret Key"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Secret'; + } + + field(5; "Tools Selection Prompt"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Tools Selection Prompt'; + } + + field(10; "Tool 1 Prompt"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Tool 1 Prompt'; + } + + field(11; "Tool 1 Definition"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Tool 1 Definition'; + } + + field(20; "Tool 2 Prompt"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Tool 2 Prompt'; + } + field(21; "Tool 2 Definition"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Tool 2 Definition'; + } + + field(31; "Tool 3 Definition"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Tool 3 Definition'; + } + + field(41; "Tool 4 Definition"; Guid) + { + DataClassification = CustomerContent; + Caption = 'Tool 4 Definition'; + } + + field(50; "Embeddings Deployment"; Text[250]) + { + DataClassification = CustomerContent; + Caption = 'Embeddings Deployment'; + } + + } + + keys + { + key(PK; Code) + { + Clustered = true; + } + } + + procedure GetEndpoint() Endpoint: Text[250] + var + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + begin + NoSeriesCopilotSetup.Get(); + NoSeriesCopilotSetup.TestField(NoSeriesCopilotSetup.Endpoint); + exit(NoSeriesCopilotSetup.Endpoint); + end; + + procedure GetDeployment() Deployment: Text[250] + var + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + begin + NoSeriesCopilotSetup.Get(); + NoSeriesCopilotSetup.TestField(NoSeriesCopilotSetup.Deployment); + exit(NoSeriesCopilotSetup.Deployment); + end; + + procedure GetEmbeddingsDeployment() Deployment: Text[250] + var + NoSeriesCopilotSetup: Record "No. Series Copilot Setup"; + begin + NoSeriesCopilotSetup.Get(); + NoSeriesCopilotSetup.TestField(NoSeriesCopilotSetup."Embeddings Deployment"); + exit(NoSeriesCopilotSetup."Embeddings Deployment"); + end; + + + [NonDebuggable] + procedure GetSecretKeyFromIsolatedStorage() SecretKey: Text + begin + if not IsNullGuid(Rec."Secret Key") then + if not IsolatedStorage.Get(Rec."Secret Key", DataScope::Module, SecretKey) then; + + exit(SecretKey); + end; + + [NonDebuggable] + procedure SetSecretKeyToIsolatedStorage(SecretKey: Text) + var + NewSecretGuid: Guid; + begin + if not IsNullGuid(Rec."Secret Key") then + if not IsolatedStorage.Delete(Rec."Secret Key", DataScope::Module) then; + + NewSecretGuid := CreateGuid(); + + IsolatedStorage.Set(NewSecretGuid, SecretKey, DataScope::Module); + + Rec."Secret Key" := NewSecretGuid; + end; + + [NonDebuggable] + procedure GetToolsSelectionPromptFromIsolatedStorage() ToolsSelectionPrompt: Text + begin + if not IsNullGuid(Rec."Tools Selection Prompt") then + if not IsolatedStorage.Get(Rec."Tools Selection Prompt", DataScope::Module, ToolsSelectionPrompt) then; + + exit(ToolsSelectionPrompt); + end; + + [NonDebuggable] + procedure SetToolsSelectionPromptToIsolatedStorage(ToolsSelectionPrompt: Text) + var + NewToolsSelectionPromptGuid: Guid; + begin + if not IsNullGuid(Rec."Tools Selection Prompt") then + if not IsolatedStorage.Delete(Rec."Tools Selection Prompt", DataScope::Module) then; + + NewToolsSelectionPromptGuid := CreateGuid(); + + IsolatedStorage.Set(NewToolsSelectionPromptGuid, ToolsSelectionPrompt, DataScope::Module); + + Rec."Tools Selection Prompt" := NewToolsSelectionPromptGuid; + end; + + [NonDebuggable] + procedure GetTool1PromptFromIsolatedStorage() FunctionsPrompt: Text + begin + if not IsNullGuid(Rec."Tool 1 Prompt") then + if not IsolatedStorage.Get(Rec."Tool 1 Prompt", DataScope::Module, FunctionsPrompt) then; + + exit(FunctionsPrompt); + end; + + [NonDebuggable] + procedure SetTool1PromptToIsolatedStorage(FunctionsPrompt: Text) + var + NewFunctionsPromptGuid: Guid; + begin + if not IsNullGuid(Rec."Tool 1 Prompt") then + if not IsolatedStorage.Delete(Rec."Tool 1 Prompt", DataScope::Module) then; + + NewFunctionsPromptGuid := CreateGuid(); + + IsolatedStorage.Set(NewFunctionsPromptGuid, FunctionsPrompt, DataScope::Module); + + Rec."Tool 1 Prompt" := NewFunctionsPromptGuid; + end; + + [NonDebuggable] + procedure GetTool1DefinitionFromIsolatedStorage() FunctionsPrompt: Text + begin + if not IsNullGuid(Rec."Tool 1 Definition") then + if not IsolatedStorage.Get(Rec."Tool 1 Definition", DataScope::Module, FunctionsPrompt) then; + + exit(FunctionsPrompt); + end; + + [NonDebuggable] + procedure SetTool1DefinitionToIsolatedStorage(FunctionsPrompt: Text) + var + NewFunctionsPromptGuid: Guid; + begin + if not IsNullGuid(Rec."Tool 1 Definition") then + if not IsolatedStorage.Delete(Rec."Tool 1 Definition", DataScope::Module) then; + + NewFunctionsPromptGuid := CreateGuid(); + + IsolatedStorage.Set(NewFunctionsPromptGuid, FunctionsPrompt, DataScope::Module); + + Rec."Tool 1 Definition" := NewFunctionsPromptGuid; + end; + + [NonDebuggable] + procedure GetTool2PromptFromIsolatedStorage() FunctionsPrompt: Text + begin + if not IsNullGuid(Rec."Tool 2 Prompt") then + if not IsolatedStorage.Get(Rec."Tool 2 Prompt", DataScope::Module, FunctionsPrompt) then; + + exit(FunctionsPrompt); + end; + + [NonDebuggable] + procedure SetTool2PromptToIsolatedStorage(FunctionsPrompt: Text) + var + NewFunctionsPromptGuid: Guid; + begin + if not IsNullGuid(Rec."Tool 2 Prompt") then + if not IsolatedStorage.Delete(Rec."Tool 2 Prompt", DataScope::Module) then; + + NewFunctionsPromptGuid := CreateGuid(); + + IsolatedStorage.Set(NewFunctionsPromptGuid, FunctionsPrompt, DataScope::Module); + + Rec."Tool 2 Prompt" := NewFunctionsPromptGuid; + end; + + [NonDebuggable] + procedure GetTool2DefinitionFromIsolatedStorage() FunctionsPrompt: Text + begin + if not IsNullGuid(Rec."Tool 2 Definition") then + if not IsolatedStorage.Get(Rec."Tool 2 Definition", DataScope::Module, FunctionsPrompt) then; + + exit(FunctionsPrompt); + end; + + [NonDebuggable] + procedure SetTool2DefinitionToIsolatedStorage(FunctionsPrompt: Text) + var + NewFunctionsPromptGuid: Guid; + begin + if not IsNullGuid(Rec."Tool 2 Definition") then + if not IsolatedStorage.Delete(Rec."Tool 2 Definition", DataScope::Module) then; + + NewFunctionsPromptGuid := CreateGuid(); + + IsolatedStorage.Set(NewFunctionsPromptGuid, FunctionsPrompt, DataScope::Module); + + Rec."Tool 2 Definition" := NewFunctionsPromptGuid; + end; + + [NonDebuggable] + procedure GetTool3DefinitionFromIsolatedStorage() FunctionsPrompt: Text + begin + if not IsNullGuid(Rec."Tool 3 Definition") then + if not IsolatedStorage.Get(Rec."Tool 3 Definition", DataScope::Module, FunctionsPrompt) then; + + exit(FunctionsPrompt); + end; + + [NonDebuggable] + procedure SetTool3DefinitionToIsolatedStorage(FunctionsPrompt: Text) + var + NewFunctionsPromptGuid: Guid; + begin + if not IsNullGuid(Rec."Tool 3 Definition") then + if not IsolatedStorage.Delete(Rec."Tool 3 Definition", DataScope::Module) then; + + NewFunctionsPromptGuid := CreateGuid(); + + IsolatedStorage.Set(NewFunctionsPromptGuid, FunctionsPrompt, DataScope::Module); + + Rec."Tool 3 Definition" := NewFunctionsPromptGuid; + end; + + [NonDebuggable] + procedure GetTool4DefinitionFromIsolatedStorage() FunctionsPrompt: Text + begin + if not IsNullGuid(Rec."Tool 4 Definition") then + if not IsolatedStorage.Get(Rec."Tool 4 Definition", DataScope::Module, FunctionsPrompt) then; + + exit(FunctionsPrompt); + end; + + [NonDebuggable] + procedure SetTool4DefinitionToIsolatedStorage(FunctionsPrompt: Text) + var + NewFunctionsPromptGuid: Guid; + begin + if not IsNullGuid(Rec."Tool 4 Definition") then + if not IsolatedStorage.Delete(Rec."Tool 4 Definition", DataScope::Module) then; + + NewFunctionsPromptGuid := CreateGuid(); + + IsolatedStorage.Set(NewFunctionsPromptGuid, FunctionsPrompt, DataScope::Module); + + Rec."Tool 4 Definition" := NewFunctionsPromptGuid; + end; + + [NonDebuggable] + procedure ImportFromTextFile(var ImportedText: Text) + var + FileName: Text; + InStr: InStream; + TextLine: Text; + begin + Clear(ImportedText); + if not UploadIntoStream('', '', 'Text files (*.txt)|*.txt', FileName, InStr) then + exit; + + InStr.ResetPosition(); + while not InStr.EOS do begin + InStr.Read(TextLine); + ImportedText += TextLine; + end; + end; + + procedure GetOrInsertRecord() + begin + if not Get() then + Insert(true); + end; +} +// end of \ No newline at end of file