Volodymyr Dvernytskyi
Personal blog about Navision & Dynamics 365 Business Central
results count:
post cover

What are AI Prompt Pages in Business Central? A Simple Guide.

As you have probably noticed, Business Central is actively integrating AI and providing developer tools for working with large language models. One of these tools is the new page type PromptDialog. I suggest exploring this page type along with the new AI interfaces using simple example: create Copilot for generating Payment Terms records.

Agenda

  1. Introduction
  2. Idea to generate Payment Terms
  3. Structure of the PromptDialog Page
  4. Prepare AI model via Azure OpenAI Service
  5. Register Suggest Payment Terms Copilot in BC
  6. Prompt engineering
  7. Integrate AI to PromptDialog page
  8. Suggest Payment Terms Demo

Introduction

First of all, I want to note that Microsoft and OpenAI provide very detailed documentation on working with AI and PromptPages, so I will frequently refer to the documentation. It is also worth checking out the BCTech repository, specifically the AzureOpenAI samples. While writing this post, I was inspired by these examples.

So, PromptDialog page is a unique page type designed for working with generative AI (LLM) in Business Central. The page interface is specifically created to assist users in solving various tasks. A PromptDialog page can utilize a wide range of LLM models. In our example, we will use GPT-4o from OpenAI, which we will deploy on Azure using the Azure OpenAI Service. Overall, the final result can be called a Copilot, meaning an AI-powered assistant for working in Business Central.

Idea to generate Payment Terms

I am convinced that to write an engaging post, it is essential to use practical and useful examples, and even better when they solve a specific problem. For this reason, I often create demo applications and upload them to my GitHub repository. This post is no exception, by the end, we will have a fully functional Copilot for creating Payment Terms.

The idea is quite simple: we need to generate Payment Terms by translating user preferences into structured data. Many end-users struggle with Date Formulas, which is not surprising given their complex syntax, especially for non-technical users. Our newly created extension, "Suggest Payment Terms with AI", will also assist in writing correct Date Formulas.

Structure of the PromptDialog Page

Still, we need to understand the UI structure of PromptDialog pages a bit. Since, as I already mentioned, the PromptDialog documentation is quite detailed, it makes sense to provide only the main structure without going into excessive detail.

To better understand which sections of the page are responsible for specific UI elements and functionality in a PromptPage, I will provide screenshots from a Copilot page along with a brief description of each section and the full code of the page. Additionally, I have structured the page code by using #region directive to separate different sections. Of course, the page type must be set to PromptDialog, with the mandatory property: Extensible = false.

When first opened, the Copilot page will look as shown in the screenshot below. Later, we will dive into how everything works, but for now, let's focus on the UI.

NewPaymentTermsTitle2WithAdditionalInfo.png

As we can see, the PromptDialog page consists of several main sections:

  • An input textbox, located in area(Prompt).
  • System actions, located in area(SystemActions).
  • A Prompt Guide section with example prompts for users, located in area(PromptGuide).

When selecting an action from the PromptGuide section, the page will transition to the next state, where the global text variable InputPaymentTermsDescription will be assigned one of the example prompts for the user.

PromptPageUsePromptGuideWithInfo.png

After the user finishes formulating the prompt and clicks Generate, the page will update and display several new sections, specifically:

  • Output information from the AI server, located in area(Content).
  • Additional system actions, located in area(SystemActions).
  • Information about the last user prompt, with the ability to edit it via the global text variable InputPaymentTermsDescription.

PromptPageAfterGenerationWithInfo.png

I also provide a simplified version of the page's code with a minimal structure to better understand its layout and functionality.

page 81802 "SPT SuggestPT - Proposal"
{
    PageType = PromptDialog;
    Extensible = false;
    Caption = 'Draft new payment terms with Copilot';
    DataCaptionExpression = InputPaymentTermsDescription;
    IsPreview = true;

    layout
    {
        #region input section
        area(Prompt)
        {
            field(PaymentTermsDescriptionField; InputPaymentTermsDescription)
            {
                ApplicationArea = All;
                ShowCaption = false;
                MultiLine = true;
                InstructionalText = 'Describe the payment terms you want to create with Copilot vld-bc.com';
            }
        }
        #endregion

        #region output section
        area(Content)
        {
            part(ProposalDetails; "SPT Payment Terms ProposalSub.")
            {
                Caption = 'Payment Terms';
                ShowFilter = false;
                ApplicationArea = All;
            }
        }
        #endregion
    }
    actions
    {
        #region prompt guide
        area(PromptGuide)
        {
            action(NetDate)
            {
                ApplicationArea = All;
                Caption = 'Net date';
                ToolTip = 'Net date';

                trigger OnAction()
                begin
                    InputPaymentTermsDescription := 'Create payment terms where the due date is [DueDateNumberOfDays] days from the invoice date.';
                end;
            }
        }
        #endregion

        #region system actions
        area(SystemActions)
        {
            systemaction(Generate)
            {
                Caption = 'Generate';
                ToolTip = 'Generate a payment terms with Dynamics 365 Copilot.';

                trigger OnAction()
                begin
                    RunGeneration();
                end;
            }
            systemaction(OK)
            {
                Caption = 'Keep it';
                ToolTip = 'Save the Payment Terms proposed by Dynamics 365 Copilot.';
            }
            systemaction(Cancel)
            {
                Caption = 'Discard it';
                ToolTip = 'Discard the Payment Terms proposed by Dynamics 365 Copilot.';
            }
            systemaction(Regenerate)
            {
                Caption = 'Regenerate';
                ToolTip = 'Regenerate the Payment Terms proposed by Dynamics 365 Copilot.';

                trigger OnAction()
                begin
                    RunGeneration();
                end;
            }
            #endregion
        }
    }
    var
        InputPaymentTermsDescription: Text;
}

Prepare AI model via Azure OpenAI Service

To make this page functional, we need to connect it to an AI model, and in our case, that is OpenAI model gpt 4o. In Business Central, there are two ways to achieve this:

  • Using Managed AI Resources as I understand it, this means that the resources and model are managed by Microsoft or even Business Central itself. Unfortunately, this option is only available to certain partners.
  • Self Managed AI Resource using our own credentials.

Our choice is self managed AI resource. For this, we will use Azure Azure OpenAI Service.

Here is step by step guide:

1.Go to portal azure marketplace and search for Azure OpenAI

PortalAzureMarketplace.png

2.Create Azure OpenAI service

CreateOpenAIService.png

3.Go to Azure AI Foundry portal from created service

AzureAIFoundryPortal.png

4.Select chat completion model gpt-4o in model catalog

ModelCatalog.png

5.Deploy selected model

DeployModel.png

6.Go to your deployed model from model catalog

SeectDeployedModel.png

7.Open in Playground, also here you can see your API key and model name.

OpenInPlayground.png

8.Go to View code to see code examples. In chat playground windows we can chat with our model as well.

ChatPlayground.png

9.Code samples is useful if you would like to use your model via some programming language as python etc. In our case we would like to get endpoint URL and model name.

SampleCode.png

As I specified before, we are interested in three key values required to use this model in the PromptDialog page:

  • Model name – the name we used when creating the model (in my case, it matches the model’s actual name).
  • Endpoint URL – the URL taken from the sample code screenshot.
  • API Key – the key used to authenticate access.

Register Suggest Payment Terms Copilot in BC

Before integration the AI model into the PromptDialog, we need to register our Copilot capability. To do this, we must extend the Copilot Capability enum and add our new Copilot: "Suggest Payment Terms". After that, we can create an Installation codeunit, which will automatically register "Suggest Payment Terms" during installation.

enumextension 81801 "SPT Copilot Capability" extends "Copilot Capability" //2000000003
{
    value(81801; "SPT Suggest Payment Terms")
    {
        Caption = 'Suggest Payment Terms';
    }
}
codeunit 81803 "SPT Install"
{
    Subtype = Install;
    InherentEntitlements = X;
    InherentPermissions = X;
    Access = Internal;

    trigger OnInstallAppPerDatabase()
    begin
        RegisterCapability();
    end;

    local procedure RegisterCapability()
    var
        CopilotCapability: Codeunit "Copilot Capability";
        LearnMoreUrlTxt: Label 'https://vld-bc.com/blog/ai-prompt-pages-in-bc', Locked = true;
    begin
        if not CopilotCapability.IsCapabilityRegistered(Enum::"Copilot Capability"::"SPT Suggest Payment Terms") then
            CopilotCapability.RegisterCapability(Enum::"Copilot Capability"::"SPT Suggest Payment Terms", Enum::"Copilot Availability"::Preview, LearnMoreUrlTxt);
    end;
}

After that, "Suggest Payment Terms" will appear in the list of available capabilities on the "Copilot & AI Capabilities" page. Note: As a best practice, it is recommended to initially position your solution as a preview. This allows time to gather user feedback and ensure the solution works effectively.

RegisteredCopilot.png

Prompt engineering

Now, let's talk a bit about the structure of requests to OpenAI GPT-4o and how to properly craft prompts. I highly recommend reading about prompt engineering best practices from OpenAI and documentation from Microsoft.

In short, a request consists of:

  • System Message – information for the model that defines the context of the request and provides general instructions.
  • User Message – the specific request from the user within the context of the System Message.

Of course, this is a very brief explanation and doesn't cover everything, but it's enough to get started.

To achieve a structured response from GPT-4o, we can use function calling, also read Microsoft documentation. This allows generative AI to be applied in scenarios such as executing code or interacting with external services. Essentially, it involves defining a function and its parameters so that the function can be called within a request to execute specific code. In our case, this function will be used to create Payment Terms.

So, to achieve the creation of Payment Terms, we need to define:

  • System Message
  • AI Function
  • User Messages needed for the PromptGuide.

I suggest starting with the creation of the AI Function for generating Payment Terms. I named this function create_paymentterms and added several parameters that correspond to the fields in Business Central that I want to populate using AI. Each of these fields has a name, type, and description. Here is the final function for review:

{
  "type": "function",
  "function": {
    "name": "create_paymentterms",
    "description": "Call this function to create a new payment terms",
    "parameters": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "description": "A short code for payment terms (max 10 characters)."
        },
        "desc": {
          "type": "string",
          "description": "A short description for this payment terms (max 100 characters)."
        },
        "dueDateCalculation": {
          "type": "string",
          "description": "Specifies a date formula for calculating the due date relative to the invoice date. If a fixed day-of-month is mentioned (e.g., 20th), convert it to the corresponding Business Central formula (e.g., -CM+19D). Valid examples: 3D, -CM+7D, 1W, CW+1D."
        },
        "discountDateCalculation": {
          "type": "string",
          "description": "Specifies the date formula for the discount date relative to the invoice date. If a fixed day-of-month is mentioned (e.g., 12th), convert it to the corresponding Business Central formula (e.g., -CM+11D). Otherwise, use a relative day count. Valid examples: 3D, -CM+7D, 1W, CW+1D. Empty string if not applicable."
        },
        "discountPercent": {
          "type": "number",
          "description": "Specifies the discount percent if applicable, or zero if not."
        }
      },
      "required": [
        "code",
        "desc",
        "dueDateCalculation",
        "discountDateCalculation",
        "discountPercent"
      ]
    }
  }
}

As you may have noticed, for the Date Formula fields, I provided a more detailed description, since this format is quite unique and requires a clear explanation for the AI on how to handle it. I also specified maximum lengths where necessary and allowed for empty values in certain fields where applicable.

Next, let's create the System Prompt, where we will define the context of the task and provide instructions. We will also specify that to create Payment Terms, the previously created function create_paymentterms must be called.

The user will describe Payment Terms. Your task is to prepare the payment terms with the described date formulas for Microsoft Dynamics 365 Business Central.

**Important:**
- If the user refers to a fixed day-of-month (e.g., "12th", "20th"), convert that into a valid Business Central date formula using the rule: For day n, use "-CM+(n-1)D". For instance, "12th" becomes "-CM+11D" and "20th" becomes "-CM+19D".
- If the user refers to a relative number of days (e.g., "20 days from invoice date"), use that number directly (e.g., "20D").
- Validate the computed date formulas with examples for invoice dates before and after the computed dates.
- Follow the valid Business Central syntax (e.g., 3D, -CM+7D, 1W, CW+1D).

Call the function "create_paymentterms" to create the payment terms.

The last step is to provide User Message examples for the Prompt Guide. I want to emphasize that it is very important to include a guide for users so they understand how to properly formulate prompts. I suggest to read about it. Here is some examples of prompt guides for suggesting Payment Terms:

Create payment terms where the due date is [DueDateNumberOfDays] days from the invoice date.
Create payment terms where the due date is [DueDateNumberOfDays] days after end of current month from the invoice date.
Generate Payment Terms for every [DiscountDateDayOfMonth]th of the next month with a payment discount of [PaymentDiscountPercent]% and due date on the [DueDateDayOfMonth]th of the next month.

Integrate AI to PromptDialog page

Now we are ready for the practical implementation of our request to the AI model in Business Central using AL. To facilitate this, Microsoft has introduced several interfaces for working with OpenAI models. For example, to implement an AI function, there is the AOAI Function interface. Here is an example of its implementation:

codeunit 81802 "SPT Create Payment Terms" implements "AOAI Function"
{
    var
        FunctionNameLbl: Label 'create_paymentterms', Locked = true;

    procedure GetPrompt(): JsonObject
    var
        ToolDefinition: JsonObject;
        FunctionDefinition: JsonObject;
        ParametersDefinition: JsonObject;
    begin
        ParametersDefinition.ReadFrom(
            '{"type": "object",' +
            '"properties": {' +
                '"code": { "type": "string", "description": "A short code for payment terms (max 10 characters)."},' +
                '"desc": { "type": "string", "description": "A short description for this payment terms (max 100 characters)."},' +
                '"dueDateCalculation": { "type": "string", "description": "Specifies a date formula for calculating the due date relative to the invoice date. If a fixed day-of-month is mentioned (e.g., 20th), convert it to the corresponding Business Central formula (e.g., -CM+19D). Valid examples: 3D, -CM+7D, 1W, CW+1D."},' +
                '"discountDateCalculation": { "type": "string", "description": "Specifies the date formula for the discount date relative to the invoice date. If a fixed day-of-month is mentioned (e.g., 12th), convert it to the corresponding Business Central formula (e.g., -CM+11D). Otherwise, use a relative day count. Valid examples: 3D, -CM+7D, 1W, CW+1D. Empty string if not applicable."},' +
                '"discountPercent": { "type": "number", "description": "Specifies the discount percent if applicable, or zero if not."}' +
            '},"required": ["code", "desc", "dueDateCalculation", "discountDateCalculation", "discountPercent"]}'
            );

        FunctionDefinition.Add('name', FunctionNameLbl);
        FunctionDefinition.Add('description', 'Call this function to create a new payment terms');
        FunctionDefinition.Add('parameters', ParametersDefinition);

        ToolDefinition.Add('type', 'function');
        ToolDefinition.Add('function', FunctionDefinition);

        exit(ToolDefinition);
    end;

    procedure Execute(Arguments: JsonObject): Variant
    var
        Code, Description, DueDateCalculation, DiscountDateCalculation, DiscountPercent : JsonToken;
    begin
        Arguments.Get('code', Code);
        Arguments.Get('desc', Description);
        Arguments.Get('dueDateCalculation', DueDateCalculation);
        Arguments.Get('discountDateCalculation', DiscountDateCalculation);
        Arguments.Get('discountPercent', DiscountPercent);

        TempPaymentTerms.Init();
        TempPaymentTerms.Code := Code.AsValue().AsCode();
        TempPaymentTerms.Description := Description.AsValue().AsText();
        Evaluate(TempPaymentTerms."Due Date Calculation", DueDateCalculation.AsValue().AsText());
        if DiscountDateCalculation.AsValue().AsText() <> '' then
            Evaluate(TempPaymentTerms."Discount Date Calculation", DiscountDateCalculation.AsValue().AsText());
        TempPaymentTerms."Discount %" := DiscountPercent.AsValue().AsDecimal();
        TempPaymentTerms.Insert();
        exit('Completed creating payment terms');
    end;

    procedure GetName(): Text
    begin
        exit(FunctionNameLbl);
    end;

    procedure GetPaymentTerms(var LocalPaymentTerms: Record "Payment Terms" temporary)
    begin
        LocalPaymentTerms.Copy(TempPaymentTerms, true);
    end;

    var
        TempPaymentTerms: Record "Payment Terms" temporary;
}

The GetPrompt() function essentially creates the structure of the AI function, while the Execute() function processes the returned result in the format defined by the AI function. As you can see in Execute() we actually generate temporary record Payment Terms as output for user.

Now, we just need to authenticate, pass the AI function, System Prompt, and User Prompt. After that, we can retrieve the result of the AI function execution using the GetPaymentTerms method in our implementation of the AOAI Function interface.

local procedure GeneratePaymentTermProposal()
var
    SuggestPaymentTermsSetup: Record "SPT PT AI Setup";
    AzureOpenAI: Codeunit "Azure OpenAI";
    AOAIOperationResponse: Codeunit "AOAI Operation Response";
    AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params";
    AOAIChatMessages: Codeunit "AOAI Chat Messages";
    SuggestPaymentTerms: Codeunit "SPT Create Payment Terms";
begin
    SuggestPaymentTermsSetup.Get();
    SuggestPaymentTermsSetup.TestField("Endpoint URL");
    SuggestPaymentTermsSetup.TestField("Model Name");

    AzureOpenAI.SetAuthorization(Enum::"AOAI Model Type"::"Chat Completions",
        SuggestPaymentTermsSetup."Endpoint URL", SuggestPaymentTermsSetup."Model Name", SuggestPaymentTermsSetup.GetSecret());

    AzureOpenAI.SetCopilotCapability(Enum::"Copilot Capability"::"SPT Suggest Payment Terms");

    AOAIChatCompletionParams.SetMaxTokens(2500);
    AOAIChatCompletionParams.SetTemperature(0);

    AOAIChatMessages.AddSystemMessage(GetSystemPrompt());
    AOAIChatMessages.AddUserMessage(UserPrompt);

    AOAIChatMessages.AddTool(SuggestPaymentTerms);
    AOAIChatMessages.SetToolInvokePreference("AOAI Tool Invoke Preference"::Automatic);
    AOAIChatMessages.SetToolChoice('auto');

    AzureOpenAI.GenerateChatCompletion(AOAIChatMessages, AOAIChatCompletionParams, AOAIOperationResponse);

    if not AOAIOperationResponse.IsSuccess() then
        Error(AOAIOperationResponse.GetError());

    SuggestPaymentTerms.GetPaymentTerms(TempPaymentTerms);
end;

Some interesting parameters used in the implementation are worth noting:

  • SetMaxTokens – Defines the maximum number of tokens allowed for the generated response. To optimize cost, it's best to specify a reasonable limit.
  • SetTemperature – Controls the creativity of the response. A lower value means less creativity but more predictability. Since our task requires precision, setting the temperature to 0 is the best choice.
  • SetToolChoice – Specifies the strategy for calling AI functions. For example, it determines how many times the function can be invoked. Auto means it can be called zero, one, or multiple times as needed.

The remaining question is: how will users access our new Copilot – "Suggest Payment Terms"? To enable this, we simply need to add our page as an action in the Prompting category.

pageextension 81801 "SPT Payment Terms" extends "Payment Terms" //4
{
    actions
    {
        addfirst(Category_New)
        {
            actionref(SPTGenerateCopilotPromoted; SPTGenerateCopilotAction)
            {
            }
        }

        addlast(Prompting)
        {
            action(SPTGenerateCopilotAction)
            {
                Caption = 'Draft with Copilot';
                Ellipsis = true;
                ApplicationArea = All;
                ToolTip = 'Lets Copilot generate a draft payment terms based on your description.';
                Image = Sparkle;

                trigger OnAction()
                begin
                    Page.RunModal(Page::"SPT SuggestPT - Proposal");
                end;
            }
        }
    }
}

This will add special AI action "Draft with Copilot" to the page we want, in our case it's Payment Terms.

PaymentTermsPage.png

Suggest Payment Terms Demo

To get started, go to the "Suggest Payment Terms Setup" page and fill in the information we previously obtained from Azure OpenAI Service: Model name, Endpoint URL, API Key.

SuggestPaymentTermsSetup.png

Access to the newly created Copilot is available on the Payment Terms page.

SuggestPaymentTermsDemo.gif

Source Code

https://github.com/Drakonian/suggest-payment-terms-with-ai

Back to top