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.
Core objects
| Object | Role |
|---|---|
"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; returnstrueon success andfalseon failure
Multi-step generation
Pass a tool set into GenerateText with a step budget:
Result := Client.GenerateText(Model, Request, ToolSet, MaxSteps);
The client:
- Sends the prompt and tool definitions to the model.
- Executes any tools requested by the model.
- Sends the tool results back to the model.
- Repeats until the model returns a final answer or
MaxStepsis reached.
MaxSteps values below 1 are treated as 1. Each generation step counts once toward the limit. Client.GenerateText(Model, Request, ToolSet) without MaxSteps allows 5 steps.
Outcomes
| Condition | Result |
|---|---|
| Model returns text (no tool calls) | Success; Result.Output() is the final answer |
| Model requests a registered tool | Tool runs and the loop continues |
| Model requests an unknown tool name | Generation fails with an invalid-request error |
Tool Execute returns false | The tool result is sent back to the model and the loop continues |
| Last step still has tool calls | The result reports Result.HasToolCalls() and Result.StoppedAtStepLimit() as true |
Structured output and RecRef binding run on the final non-tool-call step only.
A run, end to end
With the echo tool registered and a prompt of Echo the word ping, the loop takes two model calls. The model asks for the tool, the client runs it and sends the result back, and the second response is the final answer:
| Step | Model returns | Client does |
|---|---|---|
| 1 | tool call echo with {"message":"ping"} | Runs Execute, appends ping as the tool result |
| 2 | The echo tool returned: ping | Stops, because there are no tool calls left |
The result reflects the whole run, not just the last call:
| Accessor | Value |
|---|---|
Result.Output() | The echo tool returned: ping |
Result.GetStepCount() | 2 |
Result.HasToolCalls() | false |
Result.StoppedAtStepLimit() | false |
Had MaxSteps been 1, the loop would stop after step 1 and the result would indicate that more steps were needed. Values below 1 are treated as 1.
Choose a registration pattern
| Pattern | API | Fit |
|---|---|---|
| Interface tool | ToolSet.Add(Tool) | Reusable single-tool codeunits |
| Handler pack | ToolSet.Use(Handler) | Several tools behind one object ID |
| Named + event | ToolSet.Add(Name, Description, Schema) | Definitions at the call site; execute via OnBeforeExecuteTool |
| Definition object | ToolSet.Add(Schema.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 OnBeforeExecuteTool.
Interface tools
Implement "AIOS Tool" and add the codeunit to the set.
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;
}
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 Schema.ToolDefinition(...) on "AIOS Schema".
ToolSet.Use(Handler):
- Stores the handler for execution
- Adds every definition from
Handler.GetDefinitions()
Call Use once per tool set. A second call raises an error, so combine tools in one handler or add the rest with Add.
codeunit 50101 "My App Tools" implements "AIOS Tool Handler"
{
procedure GetDefinitions(): JsonArray
var
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(
Schema.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;
}
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 OnBeforeExecuteTool
Add a named definition to the tool set and handle execution in an OnBeforeExecuteTool subscriber on "AIOS Tool Set". Set Handled := true after matching the name.
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:
ToolSet.Add(
Schema.ToolDefinition(
'echo',
'Echoes the message argument back unchanged.',
Schema.Object(Fields)));
Subscriber shape:
[EventSubscriber(ObjectType::Codeunit, Codeunit::"AIOS Tool Set", 'OnBeforeExecuteTool', '', false, false)]
local procedure OnBeforeExecuteTool(
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 and set Succeeded to the tool outcome.
Manual step control
Use Client.GenerateText(Model, Request) when you want one model call per invocation. Attach tools with Request.SetTools(ToolSet), then continue the conversation yourself.
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;
| Helper | Purpose |
|---|---|
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
| Accessor | Meaning |
|---|---|
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 before a final answer |
Result.GetStepCount() | Number of generation steps |
Result.GetTotalInputTokens() / Result.GetTotalOutputTokens() | Aggregated usage across steps |
Tool Set reference
| Procedure | Behavior |
|---|---|
Add(Tool) | Adds an "AIOS Tool". Empty or duplicate names raise an error |
Use(Handler) | Sets the handler and adds all GetDefinitions() entries. Allowed once per tool set |
Add(Name, Description, Parameters) | Adds a named tool, executed by the handler from Use or by OnBeforeExecuteTool |
Add(Definition) | Adds a named tool from a { name, description, parameters } object, such as one built with Schema.ToolDefinition(...) |
Execute(Name, Arguments, ResultText) | Runs the tool and returns whether it succeeded. Unknown names, or named tools with no handler and no subscriber, raise an error |
GetDefinitions() | Combined definitions for interface and named tools |
Count() | Number of tools in the set |
HasTool(Name) | true when a tool with that name was added |
ClearTools() | Removes all tools and the handler so the set can be rebuilt |
Schema.ToolDefinition(Name, Description, Parameters) on "AIOS Schema" builds one definition object for GetDefinitions or ToolSet.Add(Definition).
Examples
The toolkit repo ships an Examples app with demo code. It is not part of the SDK: read it for patterns, but do not depend on it from your extension. The demo codeunit "AIOS Usage Example" covers each pattern:
| Procedure | Pattern |
|---|---|
RunTools_AddInterfaceTool | Add(Tool) |
RunTools_UseHandler | Use(Handler) |
RunTools_NamedEscapeHatch | Named Add + OnBeforeExecuteTool |
RunTools_MixAddAndUse | Interface + handler on one set |
RunTools_ManualContinue | Manual step control |
Interactive UI: page "AIOS Toolkit Demo".