Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ permissionset 330 "No. Series Copilot - Objects"
Access = Internal;
Assignable = false;
Permissions =
// start of <todo>
// 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 <todo>
codeunit "No. Series Copilot Impl." = X,
codeunit "No. Series Cop. Semantic Impl." = X,
codeunit "No. Series Text Match Impl." = X;
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,22 @@ codeunit 324 "No. Series Copilot Impl."
[NonDebuggable]
local procedure GetToolsSelectionSystemPrompt() ToolsSelectionSystemPrompt: SecretText
var
// start of <todo>
// TODO: Remove this once the semantic search is implemented in production.
NoSeriesCopilotSetup: Record "No. Series Copilot Setup";
// end of <todo>
AzureKeyVault: Codeunit "Azure Key Vault";
Telemetry: Codeunit Telemetry;
ToolsSelectionPrompt: Text;
begin
// start of <todo>
// 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 <todo>
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotToolsSelectionPromptV2', ToolsSelectionPrompt) then begin
Telemetry.LogMessage('0000NDY', TelemetryToolsSelectionPromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata);
Error(ToolLoadingErr);
Expand All @@ -159,6 +171,10 @@ codeunit 324 "No. Series Copilot Impl."

local procedure GenerateNoSeries(SystemPromptTxt: SecretText; InputText: Text): Text
var
// start of <todo>
// TODO: Remove this once the semantic search is implemented in production.
NoSeriesCopilotSetup: Record "No. Series Copilot Setup";
// end of <todo>
AzureOpenAI: Codeunit "Azure OpenAI";
AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params";
AOAIOperationResponse: Codeunit "AOAI Operation Response";
Expand All @@ -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>
// 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 <todo>
AzureOpenAI.SetAuthorization(Enum::"AOAI Model Type"::"Chat Completions", AOAIDeployments.GetGPT4oLatest());
AzureOpenAI.SetCopilotCapability(Enum::"Copilot Capability"::"No. Series Copilot");
AOAIChatCompletionParams.SetMaxTokens(MaxOutputTokens());
AOAIChatCompletionParams.SetTemperature(0);
Expand All @@ -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>
// TODO: If semantic vocabulary is not required, we can remove the code.
AddNoSeriesIntent.UpdateSemanticVocabulary();
ChangeNoSeriesIntent.UpdateSemanticVocabulary();
// end of <todo>
end;

exit(CompletionAnswerTxt);
end;
Expand Down Expand Up @@ -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);
Expand All @@ -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";
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -454,6 +480,7 @@ codeunit 324 "No. Series Copilot Impl."
begin
ReadGeneratedNumberSeriesJArray(Completion).WriteTo(NoSeriesArrText);
ReassembleDuplicates(NoSeriesArrText);
ExcludeExistingNoSeriesIfNewWhereGenerated(NoSeriesArrText);

Json.InitializeCollection(NoSeriesArrText);

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ pageextension 324 "No. Series Ext." extends "No. Series"
Image = Sparkle;
ApplicationArea = All;
Visible = CopilotActionsVisible;
Enabled = CopilotActionsVisible;

trigger OnAction()
var
Expand All @@ -28,6 +27,27 @@ pageextension 324 "No. Series Ext." extends "No. Series"
end;
}
}
// start of <todo>
// 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 <todo>
}

trigger OnOpenPage()
Expand Down
Loading
Loading