AI Open SDKfor Business Central

Tools

Let models call AL procedures through AIOS Tool Set.

Tools are AL procedures the model can invoke during generation. A tool call is a request from the model; a tool result is the text your procedure returns. The SDK maps both through the provider chat format and keeps them on the conversation.

Core objects

ObjectRole
"AIOS Tool"One tool: name, description, JSON Schema input, and Execute
"AIOS Tool Handler"Many tools in one codeunit: GetDefinitions + Execute(Name, ...)
"AIOS Tool Set"Collection you pass into GenerateText
"AIOS Tool Call"One model request: id, name, arguments

Every tool exposes:

  • Name: stable identifier sent to the provider (for example echo)
  • Description: text that influences when the model selects the tool
  • Input schema: JSON Schema for arguments, usually built with "AIOS Schema"
  • Execute: runs the tool and writes ResultText; returns true on success and false on failure

Multi-step generation

Pass a tool set into GenerateText with a step budget:

MultiStep.al
Result := Client.GenerateText(Model, Request, ToolSet, MaxSteps);

The client:

  1. Attaches the tool set to the request
  2. Calls the model
  3. When the response contains tool calls, executes each tool and appends results to the request
  4. Calls the model again
  5. Repeats until the model returns a final answer or MaxSteps is reached

MaxSteps values below 1 are treated as 1. Each model HTTP call counts as one step.

Outcomes

ConditionResult
Model returns text (no tool calls)Success; Output() is the final answer
Model requests a registered toolTool runs; result is appended; loop continues
Model requests an unknown tool nameGeneration fails with an invalid-request error
Tool Execute returns falseResultText is sent back as the tool result; the loop continues
Last step still has tool callsSuccess; HasToolCalls() and StoppedAtStepLimit() are true; a warning of type tool_loop_step_limit is added

Structured output and RecRef binding run on the final non-tool-call step only.

Reasoning fields such as reasoning_content (for example DeepSeek via OpenCode Zen) are preserved on assistant tool-call turns and echoed on follow-up steps.

Choose a registration pattern

PatternAPIFit
Interface toolToolSet.Add(Tool)Reusable single-tool codeunits
Handler packToolSet.Use(Handler)Several tools behind one object ID
Named + eventToolSet.Add(Name, Description, Schema)Definitions at the call site; execute via OnExecuteTool
Definition objectToolSet.Add(ToolDefinition(...))Same as named Add, from a JSON definition

You can combine interface tools and named tools on the same "AIOS Tool Set". Execution order for a name is: interface tool, then handler (if set), then OnExecuteTool.

Interface tools

Implement "AIOS Tool" and add the codeunit to the set.

MyEchoTool.al
codeunit 50100 "My Echo Tool" implements "AIOS Tool"
{
    procedure Name(): Text
    begin
        exit('echo');
    end;

    procedure Description(): Text
    begin
        exit('Echoes the message argument back to the model.');
    end;

    procedure InputSchema(): JsonObject
    var
        Schema: Codeunit "AIOS Schema";
        Fields: List of [JsonObject];
    begin
        Fields.Add(Schema.Field('message', Schema.String()));
        exit(Schema.Object(Fields));
    end;

    procedure Execute(Arguments: JsonObject; var ResultText: Text): Boolean
    var
        Token: JsonToken;
    begin
        if not Arguments.Get('message', Token) then begin
            ResultText := 'echo tool requires a message argument.';
            exit(false);
        end;
        ResultText := Token.AsValue().AsText();
        exit(true);
    end;
}
AddInterfaceTool.al
ToolSet: Codeunit "AIOS Tool Set";
Echo: Codeunit "My Echo Tool";
Request: Record "AIOS Chat Request";
Result: Codeunit "AIOS Generate Result";
begin
    ToolSet.Add(Echo);
    Request.SetPrompt('Use the echo tool when helpful');
    Result := Client.GenerateText(Model, Request, ToolSet, 5);
end;

Handler packs

Implement "AIOS Tool Handler" when several tools should share one object ID.

GetDefinitions returns a JSON array of { name, description, parameters } objects. Build each entry with ToolSet.ToolDefinition(...).

ToolSet.Use(Handler):

  1. Stores the handler for execution
  2. Registers every definition from GetDefinitions()
MyAppTools.al
codeunit 50101 "My App Tools" implements "AIOS Tool Handler"
{
    procedure GetDefinitions(): JsonArray
    var
        ToolSet: Codeunit "AIOS Tool Set";
        Schema: Codeunit "AIOS Schema";
        Definitions: JsonArray;
        Fields: List of [JsonObject];
    begin
        Fields.Add(Schema.Field('a', Schema.Number()));
        Fields.Add(Schema.Field('b', Schema.Number()));
        Definitions.Add(
            ToolSet.ToolDefinition(
                'add_numbers',
                'Adds two numbers (a and b) and returns the sum as text.',
                Schema.Object(Fields)));
        exit(Definitions);
    end;

    procedure Execute(Name: Text; Arguments: JsonObject; var ResultText: Text): Boolean
    begin
        case Name of
            'add_numbers':
                exit(AddNumbers(Arguments, ResultText));
            else begin
                ResultText := StrSubstNo('Unknown tool %1.', Name);
                exit(false);
            end;
        end;
    end;

    local procedure AddNumbers(Arguments: JsonObject; var ResultText: Text): Boolean
    var
        Token: JsonToken;
        A: Decimal;
        B: Decimal;
    begin
        if not Arguments.Get('a', Token) then begin
            ResultText := 'Missing required tool argument.';
            exit(false);
        end;
        A := Token.AsValue().AsDecimal();
        if not Arguments.Get('b', Token) then begin
            ResultText := 'Missing required tool argument.';
            exit(false);
        end;
        B := Token.AsValue().AsDecimal();
        ResultText := Format(A + B);
        exit(true);
    end;
}
UseHandler.al
ToolSet: Codeunit "AIOS Tool Set";
Handler: Codeunit "My App Tools";
Request: Record "AIOS Chat Request";
Result: Codeunit "AIOS Generate Result";
begin
    ToolSet.Use(Handler);
    Request.SetPrompt('What is 2 plus 3?');
    Result := Client.GenerateText(Model, Request, ToolSet, 5);
end;

Named tools and OnExecuteTool

Register a definition on the tool set and handle execution in an OnExecuteTool subscriber on "AIOS Tool Set".

AddNamed.al
Schema: Codeunit "AIOS Schema";
Fields: List of [JsonObject];
begin
    Fields.Add(Schema.Field('message', Schema.String()));
    ToolSet.Add('echo', 'Echoes the message argument back unchanged.', Schema.Object(Fields));
    Result := Client.GenerateText(Model, Request, ToolSet, 5);
end;

Equivalent registration from a definition object:

AddDefinition.al
ToolSet.Add(
    ToolSet.ToolDefinition(
        'echo',
        'Echoes the message argument back unchanged.',
        Schema.Object(Fields)));

Subscriber shape:

OnExecuteTool.al
[EventSubscriber(ObjectType::Codeunit, Codeunit::"AIOS Tool Set", 'OnExecuteTool', '', false, false)]
local procedure OnExecuteTool(
    Name: Text;
    Arguments: JsonObject;
    var ResultText: Text;
    var Succeeded: Boolean;
    var Handled: Boolean)
begin
    if Name <> 'echo' then
        exit;
    Succeeded := Echo(Arguments, ResultText);
    Handled := true;
end;

Set Handled := true after handling the name. Set Succeeded to the tool outcome. When Handled stays false for a registered name, generation fails with a missing-executor error.

Manual step control

Use GenerateText(Model, Request) when you want one model call per invocation. Attach tools with Request.SetTools(ToolSet), then continue the conversation yourself.

ManualContinue.al
ToolSet.Add(Echo);
Request.SetPrompt('Use the echo tool');
Request.SetTools(ToolSet);
Request.EnsureMessagesFromPrompt();

Result := Client.GenerateText(Model, Request);
if Result.HasToolCalls() then begin
    ToolCalls := Result.GetToolCalls();
    Request.AppendAssistantToolCalls(Result.Output(), ToolCalls);
    for i := 1 to ToolCalls.Count() do begin
        ToolCalls.Get(i, Call);
        ToolSet.Execute(Call.GetName(), Call.GetArguments(), ResultText);
        Request.AppendToolResult(Call.GetId(), Call.GetName(), ResultText);
    end;
    Result := Client.GenerateText(Model, Request);
end;
HelperPurpose
Request.SetTools(ToolSet)Publishes tool definitions on the request
Request.EnsureMessagesFromPrompt()Builds the message list from system + prompt when empty
Request.AppendAssistantToolCalls(...)Adds the assistant turn that requested tools
ToolSet.Execute(Name, Arguments, ResultText)Runs one tool by name
Request.AppendToolResult(Id, Name, Content)Adds the tool result message correlated by call id

Inspect the run

AccessorMeaning
Result.Output()Final text, or validated JSON when structured output applies
Result.HasToolCalls()Last model response still contains tool calls
Result.GetToolCalls()List of "AIOS Tool Call" from that response
Result.StoppedAtStepLimit()Loop ended because MaxSteps was reached with pending tool calls
Result.GetStepCount()Number of model HTTP calls in the run
Result.GetResponseCalls()Per-call metadata for each model request
Result.GetTotalInputTokens() / GetTotalOutputTokens()Aggregated usage across steps

Tool Set reference

ProcedureBehavior
Add(Tool)Registers an "AIOS Tool"; duplicate names error
Use(Handler)Sets the handler and registers all GetDefinitions() entries
Add(Name, Description, Parameters)Registers a named tool for handler or OnExecuteTool
Add(Definition)Registers from { name, description, parameters }
ToolDefinition(Name, Description, Parameters)Builds one definition object
Register(Name, Description, Parameters)Same registration as named Add
SetHandler(Handler)Sets the executor for named tools (Use already calls this)
Execute(Name, Arguments, ResultText)Runs the tool; returns success from Execute
GetDefinitions()Combined definitions for interface + named tools
Count() / HasTool(Name)Inspection helpers

Examples

Toolkit samples in "AIOS Usage Example":

ProcedurePattern
RunTools_AddInterfaceToolAdd(Tool)
RunTools_UseHandlerUse(Handler)
RunTools_AddNamed_OnExecuteToolNamed Add + OnExecuteTool
RunTools_AddDefinitionAdd(ToolDefinition(...))
RunTools_MixInterfaceAndNamedInterface + named on one set
RunTools_ManualContinueManual step control

Interactive UI: page "AIOS Toolkit Demo".

On this page