Skip to content

Commit

Permalink
[Number Series Copilot] Bug Fixes in Intent Understanding and Field V…
Browse files Browse the repository at this point in the history
…alidation (#2111)

<!-- Thank you for submitting a Pull Request. If you're new to
contributing to BCApps please read our pull request guideline below
* https://github.com/microsoft/BCApps/Contributing.md
-->

#### Summary <!-- Provide a general summary of your changes -->

This PR addresses three critical bugs in the Number Series Copilot:

1. **Bug 1: Incorrect Intent Mapping for "Generate" Command**
- The system was misinterpreting the "generate" command, often preparing
Number Series for the next period instead of creating a new one. The fix
involves updating the tools definition and prompt to clarify the
distinction between creating new Number Series and generating Number
Series for the next period.

2. **Bug 2: Incorrect Field Check for Existing Number Series**
- The system was incorrectly performing checks on all fields for
existing Number Series. The fix modifies the behavior to exclude
pre-existing Number Series from the field validation checks, ensuring
that only mandatory fields are validated for AI generated series.

3. **Bug 3: Blank Fields Causing Validation Errors**
- The system was generating Number Series without values for optional
fields, causing validation errors. The fix refines the validation logic
so that only mandatory fields are checked, allowing optional fields to
remain blank if necessary.

**Steps to Implement:**

1. Refactor the LLM prompt and tools definition to improve the
understanding of user intent between creating and preparing Number
Series for different periods.
2. Update the logic to exclude field checks for existing Number Series
and restrict checks to mandatory fields.

**Acceptance Criteria:**

- Users can create Number Series without mistakenly triggering
next-period Number Series generation when using the word "generate."
- Existing Number Series are excluded from unnecessary checks, and only
mandatory fields are validated for new series.

These fixes aim to enhance the accuracy and reliability of the Number
Series Copilot, ensuring it correctly interprets user commands and
validates the necessary fields.


#### Work Item(s) <!-- Add the issue number here after the #. The issue
needs to be open and approved. Submitting PRs with no linked issues or
unapproved issues is highly discouraged. -->

Fixes #2106 

Fixes
[AB#550892](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/550892),
[AB#544073](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/544073)
  • Loading branch information
DmitryKatson authored Oct 23, 2024
1 parent 028e207 commit 276deb8
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// ------------------------------------------------------------------------------------------------
// 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;

permissionset 330 "No. Series Copilot - Objects"
{
Access = Internal;
Assignable = false;
Permissions =
codeunit "No. Series Copilot Impl." = X,
codeunit "No. Series Text Match Impl." = X;
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ codeunit 324 "No. Series Copilot Impl."
Telemetry: Codeunit Telemetry;
ToolsSelectionPrompt: Text;
begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotToolsSelectionPrompt', ToolsSelectionPrompt) then begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotToolsSelectionPromptV2', ToolsSelectionPrompt) then begin
Telemetry.LogMessage('0000NDY', TelemetryToolsSelectionPromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata);
Error(ToolLoadingErr);
end;
Expand Down Expand Up @@ -346,52 +346,80 @@ codeunit 324 "No. Series Copilot Impl."
for i := 0 to Json.GetCollectionCount() - 1 do begin
Json.GetObjectFromCollectionByIndex(i, NoSeriesObj);
Json.InitializeObject(NoSeriesObj);
CheckTextPropertyExistAndCheckIfNotEmpty('seriesCode', Json);
CheckMaximumLengthOfPropertyValue('seriesCode', Json, 20);
CheckTextPropertyExistAndCheckIfNotEmpty('description', Json);
CheckTextPropertyExistAndCheckIfNotEmpty('startingNo', Json);
CheckMaximumLengthOfPropertyValue('startingNo', Json, 20);
CheckTextPropertyExistAndCheckIfNotEmpty('endingNo', Json);
CheckMaximumLengthOfPropertyValue('endingNo', Json, 20);
CheckTextPropertyExistAndCheckIfNotEmpty('warningNo', Json);
CheckMaximumLengthOfPropertyValue('warningNo', Json, 20);
CheckIntegerPropertyExistAndCheckIfNotEmpty('incrementByNo', Json);
CheckIntegerPropertyExistAndCheckIfNotEmpty('tableId', Json);
CheckIntegerPropertyExistAndCheckIfNotEmpty('fieldId', Json);
CheckMandatoryProperties(Json);
end;
end;

local procedure CheckMandatoryProperties(var Json: Codeunit Json)
begin
if not CheckIfNumberSeriesIsGenerated(Json) then
exit;

CheckJsonTextProperty('seriesCode', Json, true);
CheckMaximumLengthOfPropertyValue('seriesCode', Json, 20);
CheckJsonTextProperty('description', Json, true);
CheckJsonTextProperty('startingNo', Json, true);
CheckMaximumLengthOfPropertyValue('startingNo', Json, 20);
CheckJsonTextProperty('endingNo', Json, false);
CheckMaximumLengthOfPropertyValue('endingNo', Json, 20);
CheckJsonTextProperty('warningNo', Json, false);
CheckMaximumLengthOfPropertyValue('warningNo', Json, 20);
CheckJsonIntegerProperty('incrementByNo', Json, false);
CheckJsonIntegerProperty('tableId', Json, true);
CheckJsonIntegerProperty('fieldId', Json, true);
end;

local procedure CheckIfNumberSeriesIsGenerated(var Json: Codeunit Json): Boolean
var
IsExists: Boolean;
begin
Json.GetBoolPropertyValueFromJObjectByName('exists', IsExists);
exit(not IsExists);
end;

local procedure CheckIfArrayIsNotEmpty(NumberOfGeneratedNoSeries: Integer)
begin
if NumberOfGeneratedNoSeries = 0 then
Error(EmptyCompletionErr);
end;

local procedure CheckTextPropertyExistAndCheckIfNotEmpty(propertyName: Text; var Json: Codeunit Json)
local procedure CheckJsonTextProperty(propertyName: Text; var Json: Codeunit Json; IsRequired: Boolean)
var
value: Text;
begin
Json.GetStringPropertyValueByName(propertyName, value);
if value = '' then
Error(IncorrectCompletionErr, propertyName);
if not IsRequired then
exit;

if value <> '' then
exit;

Error(IncorrectCompletionErr, propertyName);
end;

local procedure CheckIntegerPropertyExistAndCheckIfNotEmpty(propertyName: Text; var Json: Codeunit Json)
local procedure CheckJsonIntegerProperty(propertyName: Text; var Json: Codeunit Json; IsRequired: Boolean)
var
PropertyValue: Integer;
begin
Json.GetIntegerPropertyValueFromJObjectByName(propertyName, PropertyValue);
if PropertyValue = 0 then
Error(IncorrectCompletionErr, propertyName);
if not IsRequired then
exit;

if PropertyValue <> 0 then
exit;

Error(IncorrectCompletionErr, propertyName);
end;

local procedure CheckMaximumLengthOfPropertyValue(propertyName: Text; var Json: Codeunit Json; maxLength: Integer)
var
value: Text;
begin
Json.GetStringPropertyValueByName(propertyName, value);
if StrLen(value) > maxLength then
Error(TextLengthIsOverMaxLimitErr, propertyName, maxLength);
if StrLen(value) <= maxLength then
exit;

Error(TextLengthIsOverMaxLimitErr, propertyName, maxLength);
end;

local procedure ReadGeneratedNumberSeriesJArray(Completion: Text) NoSeriesJArray: JsonArray
Expand Down Expand Up @@ -445,14 +473,12 @@ codeunit 324 "No. Series Copilot Impl."
var
NoSeriesCode: Text;
NoSeriesObj: Text;
IsExists: Boolean;
begin
Json.GetObjectFromCollectionByIndex(i, NoSeriesObj);
Json.InitializeObject(NoSeriesObj);
Json.GetBoolPropertyValueFromJObjectByName('exists', IsExists);
Json.GetStringPropertyValueByName('seriesCode', NoSeriesCode);

if NoSeriesCodes.Contains(NoSeriesCode) and (not IsExists) then begin
if NoSeriesCodes.Contains(NoSeriesCode) and (CheckIfNumberSeriesIsGenerated(Json)) then begin
Json.RemoveJObjectFromCollection(i);
exit;
end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,23 +114,23 @@ page 332 "No. Series Generation"
{
Caption = 'Prepare for next year';

action(SetupForNextYear)
action(PrepareForNextYear)
{
Caption = 'Set up number series for the next year';
Caption = 'Prepare number series for the next year';
ToolTip = 'Sample prompt for setting up number series for the next year.';
trigger OnAction()
begin
InputText := SetupForNextYearLbl;
InputText := PrepareForNextYearLbl;
CurrPage.Update();
end;
}
action(SetupModuleForNextYear)
action(PrepareModuleForNextYear)
{
Caption = 'Set up number series for the [sales] module for the next year';
Caption = 'Prepare number series for the [sales] module for the next year';
ToolTip = 'Sample prompt for setting up number series for a specific module for the next year. Replace [sales] with the module you want to set up number series for.';
trigger OnAction()
begin
InputText := SetupModuleForNextYearLbl;
InputText := PrepareModuleForNextYearLbl;
CurrPage.Update();
end;
}
Expand Down Expand Up @@ -178,8 +178,8 @@ page 332 "No. Series Generation"
CreateNoSeriesForModuleWithPatternLbl: Label 'Create number series for [specify here] module in the format ';
CreateNoSeriesForCompanyLbl: Label 'Create numbers series for the new company';
ChangeNumberLbl: Label 'Change the [specify here] number to ';
SetupForNextYearLbl: Label 'Set up number series for the next year';
SetupModuleForNextYearLbl: Label 'Set up number series for the [specify here] module for the next year';
PrepareForNextYearLbl: Label 'Prepare number series for the next year';
PrepareModuleForNextYearLbl: Label 'Prepare number series for the [specify here] module for the next year';

trigger OnAfterGetCurrRecord()
begin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,24 @@

namespace Microsoft.Foundation.NoSeries;

codeunit 337 "Record Match Impl."
codeunit 337 "No. Series Text Match Impl."
{
Access = Internal;
InherentPermissions = X;
InherentEntitlements = X;

procedure IsRelevant(FirstString: Text; SecondString: Text): Boolean
var
Score: Decimal;
begin
FirstString := RemoveShortWords(FirstString);
SecondString := RemoveShortWords(SecondString);

Score := CalculateStringNearness(FirstString, SecondString, GetMatchLengthTreshold(), 100) / 100;

exit(Score >= RequiredNearness());
end;

/// <summary>
/// Computes a nearness score between strings. Nearness is based on repeatedly finding longest common substrings.
/// Substring matches below Threshold are not considered.
Expand All @@ -21,7 +33,7 @@ codeunit 337 "Record Match Impl."
/// <param name="Threshold">Substring matches below Threshold are not considered</param>
/// <param name="NormalizingFactor">Max value returned by this procedure</param>
/// <returns>A number between 0 and NormalizingFactor, representing how much of the strings was matched</returns>
procedure CalculateStringNearness(FirstString: Text; SecondString: Text; Threshold: Integer; NormalizingFactor: Integer): Integer
local procedure CalculateStringNearness(FirstString: Text; SecondString: Text; Threshold: Integer; NormalizingFactor: Integer): Integer
var
Result: Text;
TotalMatchedChars: Integer;
Expand Down Expand Up @@ -98,11 +110,11 @@ codeunit 337 "Record Match Impl."
exit(MinThreshold <= Length);
end;

procedure RemoveShortWords(OriginalText: Text[250]): Text[250];
local procedure RemoveShortWords(OriginalText: Text): Text;
var
Words: List of [Text];
Word: Text[250];
Result: Text[250];
Word: Text;
Result: Text;
begin
Words := OriginalText.Split(' '); // split the text by spaces into a list of words
foreach Word in Words do // loop through each word in the list
Expand All @@ -112,4 +124,14 @@ codeunit 337 "Record Match Impl."
OriginalText := Result; // assign the result back to the text parameter
exit(OriginalText);
end;

local procedure GetMatchLengthTreshold(): Decimal
begin
exit(2);
end;

local procedure RequiredNearness(): Decimal
begin
exit(0.9)
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +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';
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.';

Expand Down Expand Up @@ -62,7 +63,9 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function"
NewNoSeriesPrompt, CustomPatternsPromptList, TablesYamlList, EmptyList : List of [Text];
NumberOfToolResponses, i, ActualTablesChunkSize : Integer;
NumberOfAddedTables: Integer;
Progress: Dialog;
begin
Progress.Open(ToolProgressDialogTextLbl);
GetTablesRequireNoSeries(Arguments, TempSetupTable, TempNoSeriesField);
ToolsImpl.GetUserSpecifiedOrExistingNumberPatternsGuidelines(Arguments, CustomPatternsPromptList, EmptyList, false);

Expand All @@ -80,7 +83,8 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function"
.Replace(NumberOfAddedTablesPlaceholderLbl, Format(ActualTablesChunkSize)));

ToolResults.Add(ToolsImpl.ConvertListToText(NewNoSeriesPrompt), ActualTablesChunkSize);
end
end;
Progress.Close();
end;

local procedure GetTablesRequireNoSeries(var Arguments: JsonObject; var TempSetupTable: Record "Table Metadata" temporary; var TempNoSeriesField: Record "Field" temporary)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +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';
ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 2. Please try again later.';

procedure GetName(): Text
Expand Down Expand Up @@ -71,12 +72,14 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function"
ChangeNoSeriesPrompt, CustomPatternsPromptList, TablesYamlList, ExistingNoSeriesToChangeList : List of [Text];
NumberOfToolResponses, i, ActualTablesChunkSize : Integer;
NumberOfChangedTables: Integer;
Progress: Dialog;
begin
if not CheckIfUserSpecifiedNoSeriesToChange(Arguments) then begin
NoSeriesCopilotImpl.SendNotification(GetLastErrorText());
exit;
end;

Progress.Open(ToolProgressDialogTextLbl);
GetTablesWithNoSeries(Arguments, TempSetupTable, TempNoSeriesField, ExistingNoSeriesToChangeList);
ToolsImpl.GetUserSpecifiedOrExistingNumberPatternsGuidelines(Arguments, CustomPatternsPromptList, ExistingNoSeriesToChangeList, UpdateForNextYear);

Expand All @@ -97,7 +100,8 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function"
.Replace(NumberOfAddedTablesPlaceholderLbl, Format(ActualTablesChunkSize)));

ToolResults.Add(ToolsImpl.ConvertListToText(ChangeNoSeriesPrompt), ActualTablesChunkSize);
end
end;
Progress.Close();
end;

[TryFunction]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ codeunit 349 "No. Series Cop. Nxt Yr. Intent" implements "AOAI Function"

var
Telemetry: Codeunit Telemetry;
FunctionNameLbl: Label 'GenerateNextYearNumberSeries', Locked = true;
FunctionNameLbl: Label 'PrepareNextYearNumberSeries', Locked = true;
TelemetryTool3DefinitionRetrievalErr: Label 'Unable to retrieve the definition for No. Series Copilot Tool 3 from Azure Key Vault.', Locked = true;
ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 3. Please try again later.';

Expand Down Expand Up @@ -45,7 +45,7 @@ codeunit 349 "No. Series Cop. Nxt Yr. Intent" implements "AOAI Function"
var
AzureKeyVault: Codeunit "Azure Key Vault";
begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool3Definition', Definition) then begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool3DefinitionV2', Definition) then begin
Telemetry.LogMessage('0000ND9', TelemetryTool3DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata);
Error(ToolLoadingErr);
end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,32 +225,21 @@ codeunit 336 "No. Series Cop. Tools Impl."

procedure IsRelevant(TableMetadata: Record "Table Metadata"; Field: Record "Field"; Entities: List of [Text]): Boolean
var
RecordMatchMgtCopy: Codeunit "Record Match Impl.";
TextMatchImpl: Codeunit "No. Series Text Match Impl.";
Entity: Text[250];
String1: Text[250];
String2: Text[250];
Score: Decimal;
begin
foreach Entity in Entities do begin
String1 := RecordMatchMgtCopy.RemoveShortWords(RemoveTextPart(TableMetadata.Caption, ' Setup') + ' ' + RemoveTextParts(Field.FieldName, GetNoSeriesAbbreviations()));
String2 := RecordMatchMgtCopy.RemoveShortWords(Entity);
Score := RecordMatchMgtCopy.CalculateStringNearness(String1, String2, GetMatchLengthThreshold(), 100) / 100;
if Score >= RequiredNearness() then
String1 := RemoveTextPart(TableMetadata.Name, '& ') + ' ' + Field.FieldName;
String2 := Entity;

if TextMatchImpl.IsRelevant(String1, String2) then
exit(true);
end;
exit(false);
end;

local procedure GetMatchLengthThreshold(): Decimal
begin
exit(2);
end;

local procedure RequiredNearness(): Decimal
begin
exit(0.9)
end;

procedure GenerateChunkedTablesListInYamlFormat(var TablesYamlList: List of [Text]; var TempSetupTable: Record "Table Metadata" temporary; var TempNoSeriesField: Record "Field" temporary; var NumberOfAddedTables: Integer)
begin
Clear(TablesYamlList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ codeunit 327 "No. Series Copilot Register"
end;

var
LearnMoreUrlTxt: Label 'https://go.microsoft.com/fwlink/?linkid=[id]', Locked = true; //TODO: replace 'id' when docs page is ready
LearnMoreUrlTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2294514', Locked = true;

}

0 comments on commit 276deb8

Please sign in to comment.