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.
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 run would stop after step 1 with HasToolCalls() and StoppedAtStepLimit() both true, and Output() would hold no final answer.
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".