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
| 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:
- Attaches the tool set to the request
- Calls the model
- When the response contains tool calls, executes each tool and appends results to the request
- Calls the model again
- Repeats until the model returns a final answer or
MaxStepsis reached
MaxSteps values below 1 are treated as 1. Each model HTTP call counts as one step.
Outcomes
| Condition | Result |
|---|---|
| Model returns text (no tool calls) | Success; Output() is the final answer |
| Model requests a registered tool | Tool runs; result is appended; loop continues |
| Model requests an unknown tool name | Generation fails with an invalid-request error |
Tool Execute returns false | ResultText is sent back as the tool result; the loop continues |
| Last step still has tool calls | Success; 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
| 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 OnExecuteTool |
| Definition object | ToolSet.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.
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 ToolSet.ToolDefinition(...).
ToolSet.Use(Handler):
- Stores the handler for execution
- Registers every definition from
GetDefinitions()
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;
}
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".
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(
ToolSet.ToolDefinition(
'echo',
'Echoes the message argument back unchanged.',
Schema.Object(Fields)));
Subscriber shape:
[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.
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 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
| Procedure | Behavior |
|---|---|
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":
| Procedure | Pattern |
|---|---|
RunTools_AddInterfaceTool | Add(Tool) |
RunTools_UseHandler | Use(Handler) |
RunTools_AddNamed_OnExecuteTool | Named Add + OnExecuteTool |
RunTools_AddDefinition | Add(ToolDefinition(...)) |
RunTools_MixInterfaceAndNamed | Interface + named on one set |
RunTools_ManualContinue | Manual step control |
Interactive UI: page "AIOS Toolkit Demo".