Building an Agent
Summary
An agent lets the model decide which action to take and then actually execute it, instead of only returning text. ADITO does not yet ship a dedicated agent framework — built-in agent logic is on the Roadmap — but a basic agent loop can already be built today in JDito by combining function calling with JDito's own process execution.
Prerequisites
- An AI alias / API key for the ADITO-LLM chat completions endpoint.
- Familiarity with function calling and tool use.
- One or more JDito processes that perform the actual actions the agent should be able to take. See JDito for the available process types.
How the loop works
An agent loop repeats three steps until the model has enough information to answer without calling another tool:
- Send the conversation, along with a
toolsdefinition, to the chat completions endpoint. - If the response contains
tool_calls, execute the corresponding JDito process for each call and collect its result. - Append the tool result to the conversation and send the request again. Repeat until the response no longer contains
tool_calls.
This loop follows the standard OpenAI tool-calling convention: an assistant message carrying tool_calls, followed by one tool message per call, each identified by tool_call_id. It is not demonstrated elsewhere in this documentation — the building blocks below show how to assemble it in JDito.
Building block 1: call the chat completions endpoint from JDito
Calling the ADITO-LLM endpoint from JDito uses the same net.createConfigForRestWebserviceCall() pattern as any other outgoing REST call, for example the one used to call the embedding model:
import { auth, net, util } from "@aditosoftware/jdito-types";
function callChatCompletions(pMessages, pTools)
{
let body = {
model: "adito-llm",
messages: pMessages,
tools: pTools,
tool_choice: "auto",
temperature: 0.15
};
let restConfig = net.createConfigForRestWebserviceCall()
.url("https://ai.adito.cloud/chat/completions")
.dataTypeAccept("application/json")
.dataTypeSend("application/json")
.dataTypeJDitoAccept(util.DATA_TEXT)
.dataTypeJDitoSend(util.DATA_TEXT)
.actionType(net.POST)
.requestEntity(JSON.stringify(body));
// Load the token from an approved secret source (alias or environment configuration),
// never hard-code it in the process.
let authConfig = auth.createConfigForOAuth2().token("myApiToken");
return net.callRestWebservice(restConfig, authConfig);
}
Building block 2: execute a tool as a JDito process
Each tool the model can call maps to one JDito process. process.execute() runs the process by name, passes the model's arguments in as local variables, and returns whatever the process returns via result.string() or result.object():
import { process } from "@aditosoftware/jdito-types";
function executeTool(pName, pArgs)
{
switch (pName)
{
case "get_contact_activities":
return JSON.parse(process.execute("GetContactActivities_process", pArgs));
default:
throw new Error("Unknown tool: " + pName);
}
}
Inside GetContactActivities_process, read the arguments with vars.get("$local.contact_name") and return the result with result.string(...) or result.object(...) — the same pattern used to build vector inputs in the vector search guide.
Building block 3: the loop
With both building blocks in place, the loop calls the model, executes any requested tools, feeds the results back, and repeats:
import { result, vars } from "@aditosoftware/jdito-types";
function runAgentLoop(pMessages, pTools)
{
for (let i = 0; i < 5; i++) // hard cap so a misbehaving loop cannot run forever
{
let response = JSON.parse(callChatCompletions(pMessages, pTools));
let choice = response.choices[0].message;
if (!choice.tool_calls || choice.tool_calls.length === 0)
return choice.content;
pMessages.push({ role: "assistant", tool_calls: choice.tool_calls, content: choice.content });
for (let toolCall of choice.tool_calls)
{
let toolResult = executeTool(toolCall.function.name, JSON.parse(toolCall.function.arguments));
pMessages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(toolResult) });
}
}
throw new Error("Agent loop did not resolve within the iteration limit.");
}
// Entry point: an executable process that starts the loop.
var messages = [
{ role: "system", content: "You are a CRM assistant. Use the available tools to look up data." },
{ role: "user", content: vars.get("$local.userQuestion") }
];
result.string(runAgentLoop(messages, tools));
tools is the same array structure used for function calling and tool use — one entry per JDito process the agent is allowed to call.
Guardrails
Treat model output as a suggestion or draft, not an authorization to act. Per AI Compliance, record changes and other side-effecting actions require explicit input from a user or a configured, auditable process — do not let a tool call write or change data without that in place.
See also: Function calling and tool use | AI Compliance | Roadmap | JDito