Code Examples
Complete AL code examples for extending Smart Agents in Business Central
This page provides complete, working AL code examples for common Smart Agents extension scenarios. Each example includes the full codeunit or object definition ready to be added to your AL project.
Example 1: Custom Data Source for Project Tracking
This example registers a custom "Project" table as a data source and provides context data to agents.
Custom Table
table 50100 "My Project"
{
Caption = 'Project';
DataClassification = CustomerContent;
fields
{
field(1; "No."; Code[20])
{
Caption = 'No.';
}
field(2; Description; Text[100])
{
Caption = 'Description';
}
field(3; "Project Manager"; Code[50])
{
Caption = 'Project Manager';
TableRelation = "User Setup"."User ID";
}
field(4; Status; Enum "My Project Status")
{
Caption = 'Status';
}
field(5; "Budget Amount"; Decimal)
{
Caption = 'Budget Amount';
}
field(6; "Actual Amount"; Decimal)
{
Caption = 'Actual Amount';
}
field(7; "Start Date"; Date)
{
Caption = 'Start Date';
}
field(8; "End Date"; Date)
{
Caption = 'End Date';
}
}
keys
{
key(PK; "No.")
{
Clustered = true;
}
key(Status; Status) { }
}
}
enum 50100 "My Project Status"
{
Extensible = true;
value(0; Planning)
{
Caption = 'Planning';
}
value(1; Active)
{
Caption = 'Active';
}
value(2; "On Hold")
{
Caption = 'On Hold';
}
value(3; Completed)
{
Caption = 'Completed';
}
}Data Source Registration and Context Provider
codeunit 50100 "My Project Data Source"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"SA Data Scope Manager",
'OnRegisterDataSources', '', false, false)]
local procedure RegisterProjectTable(var DataSourceBuffer: Record "SA Data Source Buffer" temporary)
begin
DataSourceBuffer.Init();
DataSourceBuffer."Table ID" := Database::"My Project";
DataSourceBuffer."Table Name" := 'Project';
DataSourceBuffer.Category := 'Project Management';
DataSourceBuffer.Description := 'Project tracking with budget and actuals';
DataSourceBuffer."Read Only" := true;
DataSourceBuffer.Insert();
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"SA Agent Router",
'OnCollectDataContext', '', false, false)]
local procedure ProvideProjectContext(
AgentId: Code[20];
TableId: Integer;
UserMessage: Text;
var ContextJson: JsonObject;
var Handled: Boolean)
var
Project: Record "My Project";
DataArray: JsonArray;
RowObj: JsonObject;
SummaryObj: JsonObject;
TotalBudget: Decimal;
TotalActual: Decimal;
ProjectCount: Integer;
begin
if TableId <> Database::"My Project" then
exit;
if Project.FindSet() then
repeat
Clear(RowObj);
RowObj.Add('no', Project."No.");
RowObj.Add('description', Project.Description);
RowObj.Add('manager', Project."Project Manager");
RowObj.Add('status', Format(Project.Status));
RowObj.Add('budget', Project."Budget Amount");
RowObj.Add('actual', Project."Actual Amount");
RowObj.Add('variance', Project."Budget Amount" - Project."Actual Amount");
RowObj.Add('startDate', Format(Project."Start Date", 0, '<Year4>-<Month,2>-<Day,2>'));
RowObj.Add('endDate', Format(Project."End Date", 0, '<Year4>-<Month,2>-<Day,2>'));
DataArray.Add(RowObj);
TotalBudget += Project."Budget Amount";
TotalActual += Project."Actual Amount";
ProjectCount += 1;
until Project.Next() = 0;
SummaryObj.Add('totalProjects', ProjectCount);
SummaryObj.Add('totalBudget', TotalBudget);
SummaryObj.Add('totalActual', TotalActual);
SummaryObj.Add('totalVariance', TotalBudget - TotalActual);
ContextJson.Add('summary', SummaryObj);
ContextJson.Add('projects', DataArray);
Handled := true;
end;
}Example 2: Query Validation and Content Filtering
This example demonstrates how to validate user queries before they are processed, blocking queries that reference restricted data.
codeunit 50101 "My Query Validator"
{
var
RestrictedTerms: List of [Text];
[EventSubscriber(ObjectType::Codeunit, Codeunit::"SA Agent Router",
'OnBeforeProcessMessage', '', false, false)]
local procedure ValidateQuery(
AgentId: Code[20];
var UserMessage: Text;
var ContextJson: JsonObject;
var Cancel: Boolean;
var CancelReason: Text)
begin
InitRestrictedTerms();
if ContainsRestrictedContent(UserMessage) then begin
Cancel := true;
CancelReason := 'Your query references restricted data categories. ' +
'Please contact your administrator for access to this information.';
LogBlockedQuery(AgentId, UserMessage);
exit;
end;
// Enforce query length limits
if StrLen(UserMessage) > 2000 then begin
Cancel := true;
CancelReason := 'Query exceeds the maximum length of 2000 characters. ' +
'Please shorten your question and try again.';
end;
end;
local procedure InitRestrictedTerms()
begin
if RestrictedTerms.Count > 0 then
exit;
RestrictedTerms.Add('salary');
RestrictedTerms.Add('payroll');
RestrictedTerms.Add('social security');
RestrictedTerms.Add('bank account number');
RestrictedTerms.Add('password');
end;
local procedure ContainsRestrictedContent(Message: Text): Boolean
var
Term: Text;
LowerMessage: Text;
begin
LowerMessage := LowerCase(Message);
foreach Term in RestrictedTerms do
if LowerMessage.Contains(Term) then
exit(true);
exit(false);
end;
local procedure LogBlockedQuery(AgentId: Code[20]; Message: Text)
begin
// Log the blocked query for security auditing
// Implementation depends on your logging infrastructure
end;
}Example 3: Custom Credit Tracking with Dimension Posting
This example maps Smart Agents credit consumption to Business Central dimensions for cost allocation reporting.
codeunit 50102 "My Credit Cost Allocator"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"SA Credit Tracker",
'OnCreditConsumed', '', false, false)]
local procedure AllocateCreditCost(
UserId: Code[50];
AgentId: Code[20];
CreditsConsumed: Integer;
ModelUsed: Text)
var
UserSetup: Record "User Setup";
GenJournalLine: Record "Gen. Journal Line";
CreditCostPerUnit: Decimal;
TotalCost: Decimal;
begin
// Look up the user's department for cost allocation
if not UserSetup.Get(UserId) then
exit;
// Calculate the monetary cost of credits consumed
CreditCostPerUnit := GetCreditUnitCost(ModelUsed);
TotalCost := CreditsConsumed * CreditCostPerUnit;
// Post to the AI services expense account with department dimension
CreateExpenseEntry(
UserSetup."Salespers./Purch. Code",
GetDepartmentCode(UserId),
TotalCost,
StrSubstNo('Smart Agents - %1 (%2 credits)', AgentId, CreditsConsumed)
);
end;
local procedure GetCreditUnitCost(ModelUsed: Text): Decimal
var
SASetup: Record "SA Setup";
begin
// Retrieve the per-credit unit cost from the Smart Agents setup.
// Rates vary by model tier; see the Pricing page for current values.
SASetup.Get();
exit(SASetup.GetUnitCostForModel(ModelUsed));
end;
local procedure GetDepartmentCode(UserId: Code[50]): Code[20]
var
Employee: Record Employee;
begin
Employee.SetRange("User ID", UserId);
if Employee.FindFirst() then
exit(Employee."Global Dimension 2 Code");
exit('');
end;
local procedure CreateExpenseEntry(
SalespersonCode: Code[20];
DepartmentCode: Code[20];
Amount: Decimal;
Description: Text)
begin
// Create a general journal line for the expense
// This is a simplified example — your implementation should use
// the appropriate journal template and batch for your organization
end;
}Example 4: Page Extension for Agent Context
This example extends the Customer Card to show a Smart Agents action that pre-fills the chat with customer context.
pageextension 50100 "My Customer Card Ext" extends "Customer Card"
{
actions
{
addlast("&Customer")
{
action(AskSmartAgent)
{
ApplicationArea = All;
Caption = 'Ask Smart Agent';
ToolTip = 'Open Smart Agents chat with this customer as context.';
Image = Sparkle;
trigger OnAction()
var
AgentChat: Page "SA Chat";
ContextJson: JsonObject;
begin
ContextJson.Add('customerNo', Rec."No.");
ContextJson.Add('customerName', Rec.Name);
ContextJson.Add('balance', Rec."Balance (LCY)");
ContextJson.Add('creditLimit', Rec."Credit Limit (LCY)");
ContextJson.Add('paymentTerms', Rec."Payment Terms Code");
AgentChat.SetContext(ContextJson);
AgentChat.SetSuggestedPrompt(
StrSubstNo('Tell me about customer %1 (%2)', Rec."No.", Rec.Name));
AgentChat.Run();
end;
}
}
}
}Example 5: Custom Agent Template Registration
This example creates a custom agent template that is automatically available in the template library.
codeunit 50103 "My Template Registration"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"SA Data Scope Manager",
'OnRegisterTemplates', '', false, false)]
local procedure RegisterCustomTemplates(var TemplateBuffer: Record "SA Template Buffer" temporary)
begin
TemplateBuffer.Init();
TemplateBuffer.ID := 'tmpl_custom_project';
TemplateBuffer.Name := 'Project Status Reporter';
TemplateBuffer.Description := 'Provides project status summaries, budget vs. actual analysis, and timeline tracking.';
TemplateBuffer.Category := 'Project Management';
TemplateBuffer.Model := 'Smart';
TemplateBuffer."System Prompt" :=
'You are a project management assistant. ' +
'Provide clear summaries of project status, budget utilization, and timelines. ' +
'Always include budget variance (budget minus actual) in your reports. ' +
'Flag projects where actual costs exceed budget by more than 10%.';
TemplateBuffer."Publisher" := 'My Company';
TemplateBuffer.Version := '1.0.0';
TemplateBuffer.Insert();
end;
}Project Setup
To use any of these examples, your app.json should include the Smart Agents dependency and appropriate ID ranges:
{
"id": "your-app-guid-here",
"name": "My Smart Agents Extension",
"publisher": "Your Company",
"version": "1.0.0.0",
"brief": "Custom extensions for Smart Agents",
"description": "Adds custom data sources, query validation, and cost tracking to Smart Agents.",
"privacyStatement": "",
"EULA": "",
"help": "",
"url": "",
"contextSensitiveHelpUrl": "",
"platform": "25.0.0.0",
"application": "25.0.0.0",
"idRanges": [
{
"from": 50100,
"to": 50199
}
],
"dependencies": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Smart Agents",
"publisher": "Qualia AI",
"version": "1.0.0.0"
}
],
"runtime": "14.0",
"target": "Cloud"
}