Function calling
A language model cannot read your database, price an order, or send an email —
but it can ask your application to. Function calling is the mechanism: you
declare a set of callable functions with typed arguments, the component
advertises them with every request, and when the model decides one is needed it
sends back a call instead of an answer. TTMSMCPCloudAI runs that call through
your OnExecute handler, feeds the result back to the model, and repeats until
the model produces a final answer — all inside one Execute, with the answer
arriving in OnExecuted as usual. This guide covers declaring tools and their
parameters, constraining arguments with enums and formats, structured arguments
(nested objects and arrays of objects), packaging tools into reusable tool sets,
and running several calls in parallel.
Declaring a tool
Start with the simplest useful shape — a name, a description, one typed argument, and a handler. The description is not decoration: it is the only thing the model has to decide whether the tool applies, so write it for a reader who cannot see your code.
procedure TForm1.RegisterStockTool;
var
Tool: TTMSMCPCloudAITool;
Param: TTMSMCPCloudAIParameter;
begin
Tool := AI.Tools.Add;
Tool.Name := 'get_stock_level';
Tool.Description := 'Returns the number of units currently in stock for an article';
Tool.Enabled := True;
Param := Tool.Parameters.Add;
Param.Name := 'article_code';
Param.Description := 'The article code, for example A-1043';
Param.&Type := ptString;
Param.Required := True;
Tool.OnExecute := GetStockLevel;
end;
procedure TForm1.GetStockLevel(Sender: TObject; Args: TJSONObject;
var Result: string);
var
ArticleCode: string;
begin
{ Args carries the arguments the model chose, keyed by parameter name. }
ArticleCode := Args.GetValue<string>('article_code');
{ Whatever is assigned to Result is sent back to the model as the tool
result, and the model continues from there. Plain text is fine; JSON is
easier for the model to use precisely. }
Result := Format('{"article":"%s","units":%d}',
[ArticleCode, LookupStock(ArticleCode)]);
end;
TTMSMCPCloudAITool items live in the
component's Tools collection:
| Property | Meaning |
|---|---|
Name |
The identifier the model calls. Keep it lowercase with underscores. |
Description |
What the tool does, in prose, for the model. |
Parameters |
The declared arguments — see below. |
Enabled |
When False the tool is not advertised. Default True. |
&Type |
TTMSMCPCloudAIToolType; currently ttFunction. |
Tag |
A NativeInt of your own, untouched by the component. |
OnExecute |
The handler that runs the call. |
Tools.ActiveCount reports how many are currently enabled, which is what
actually gets advertised.
The handler is a
TTMSMCPCloudAIToolExecuteEvent:
Args is a TJSONObject keyed by parameter name, and whatever you assign to
the var Result: string parameter is returned to the model as the tool result.
Plain text works; a small JSON object is easier for the model to use precisely.
Constraining arguments: enums and formats
A free-text argument invites the model to invent a value. Constraining it is usually cheaper than validating it afterwards.
procedure TForm1.RegisterReportTool;
var
Tool: TTMSMCPCloudAITool;
Param: TTMSMCPCloudAIParameter;
begin
Tool := AI.Tools.Add;
Tool.Name := 'email_sales_report';
Tool.Description := 'Emails the sales report for one region to a recipient';
{ A fixed choice. The model may only pick one of these values. }
Param := Tool.Parameters.Add;
Param.Name := 'region';
Param.Description := 'Sales region the report covers';
Param.&Type := ptEnum;
Param.Required := True;
Param.Enum.Add('emea');
Param.Enum.Add('apac');
Param.Enum.Add('americas');
{ Format sharpens a plain string - the model is told this is an address,
not free text. }
Param := Tool.Parameters.Add;
Param.Name := 'recipient';
Param.Description := 'Email address that receives the report';
Param.&Type := ptString;
Param.Format := fmtEmail;
Param.Required := True;
Param := Tool.Parameters.Add;
Param.Name := 'period_end';
Param.Description := 'Last day covered by the report';
Param.&Type := ptString;
Param.Format := fmtDate;
Param.Required := False;
Tool.OnExecute := EmailSalesReport;
end;
procedure TForm1.EmailSalesReport(Sender: TObject; Args: TJSONObject;
var Result: string);
var
Region, Recipient: string;
begin
Region := Args.GetValue<string>('region');
Recipient := Args.GetValue<string>('recipient');
SendReport(Region, Recipient);
Result := Format('Report for %s sent to %s.', [Region, Recipient]);
end;
TTMSMCPCloudAIParameter describes
one argument. Its &Type takes a
TTMSMCPCloudAIParameterType value:
| Value | Argument is |
|---|---|
ptString |
Text. |
ptNumber |
A number — integer or floating point. |
ptBoolean |
true or false. |
ptEnum |
One of the values listed in Enum. |
ptObject |
A nested object described by Properties. This is the default. |
ptArray |
A list whose element type is ArrayType. |
Format narrows a string further with a
TTMSMCPCloudAIFormat value — fmtEmail,
fmtURI, fmtDate, fmtDateTime, fmtTime, fmtHostName, fmtipv4,
fmtipv6, fmtuuid, fmtregex, fmtPhone, or fmtNone.
Required defaults to True. Mark genuinely optional arguments False,
otherwise the model will invent a value rather than omit it.
Structured arguments: objects and arrays
Some operations do not decompose into flat scalars. An order has a customer and
a repeating set of lines, and flattening that into article_1, quantity_1,
article_2 … is worse for the model than declaring the real shape.
procedure TForm1.RegisterOrderTool;
var
Tool: TTMSMCPCloudAITool;
Customer, Lines, Field: TTMSMCPCloudAIParameter;
begin
Tool := AI.Tools.Add;
Tool.Name := 'create_order';
Tool.Description := 'Creates a sales order for a customer';
{ A nested object: Type is ptObject and its shape lives in Properties. }
Customer := Tool.Parameters.Add;
Customer.Name := 'customer';
Customer.Description := 'Customer the order belongs to';
Customer.&Type := ptObject;
Customer.Required := True;
Field := Customer.Properties.Add;
Field.Name := 'id';
Field.Description := 'Customer identifier';
Field.&Type := ptString;
Field.Required := True;
Field := Customer.Properties.Add;
Field.Name := 'purchase_order';
Field.Description := 'Customer purchase order reference';
Field.&Type := ptString;
Field.Required := False;
{ An array of objects: Type is ptArray, ArrayType says what an element is,
and ArrayProperties describes the element. }
Lines := Tool.Parameters.Add;
Lines.Name := 'lines';
Lines.Description := 'Order lines';
Lines.&Type := ptArray;
Lines.ArrayType := ptObject;
Lines.Required := True;
Field := Lines.ArrayProperties.Add;
Field.Name := 'article_code';
Field.Description := 'Article being ordered';
Field.&Type := ptString;
Field.Required := True;
Field := Lines.ArrayProperties.Add;
Field.Name := 'quantity';
Field.Description := 'Number of units';
Field.&Type := ptNumber;
Field.Required := True;
Tool.OnExecute := CreateOrder;
end;
procedure TForm1.CreateOrder(Sender: TObject; Args: TJSONObject;
var Result: string);
var
Customer: TJSONObject;
Lines: TJSONArray;
begin
Customer := Args.GetValue<TJSONObject>('customer');
Lines := Args.GetValue<TJSONArray>('lines');
{ Args owns both - read them, do not free them. }
Result := Format('{"order":"%s","lines":%d}',
[StoreOrder(Customer.GetValue<string>('id'), Lines), Lines.Count]);
end;
Two collections do the work, and mixing them up is the usual mistake:
Propertiesdescribes the fields of aptObjectparameter.ArrayPropertiesdescribes the fields of one element of aptArrayparameter whoseArrayTypeisptObject.
For an array of scalars, set ArrayType to ptString or ptNumber and leave
ArrayProperties empty. Both collections are
TTMSMCPCloudAIParameters, so
nesting can go deeper where a service supports it.
In the handler, read a nested object with Args.GetValue<TJSONObject>(...) and
a list with Args.GetValue<TJSONArray>(...). Both belong to Args — read them,
do not free them.
Reusable tool sets
Tools that belong together — inventory, reporting, a CRM — are worth packaging so they can be reused across forms and applications instead of being re-declared in each one.
type
TInventoryToolSet = class(TTMSMCPCloudAIToolSet)
protected
procedure DefineTools; override;
procedure GetStockLevel(Sender: TObject; Args: TJSONObject; var Result: string);
procedure ReserveStock(Sender: TObject; Args: TJSONObject; var Result: string);
end;
procedure TInventoryToolSet.DefineTools;
var
Tool: TTMSMCPCloudAITool;
Param: TTMSMCPCloudAIParameter;
begin
inherited;
{ BeginUpdate / EndUpdate wrap the declarations. EndUpdate records each
tool's handler so it can be reattached before every request - declare
the tools and their OnExecute handlers between the two calls. }
BeginUpdate;
Tool := Tools.Add;
Tool.Name := 'get_stock_level';
Tool.Description := 'Returns the number of units in stock for an article';
Param := Tool.Parameters.Add;
Param.Name := 'article_code';
Param.Description := 'The article code to look up';
Param.&Type := ptString;
Param.Required := True;
Tool.OnExecute := GetStockLevel;
Tool := Tools.Add;
Tool.Name := 'reserve_stock';
Tool.Description := 'Reserves units of an article for an order';
Param := Tool.Parameters.Add;
Param.Name := 'article_code';
Param.Description := 'The article code to reserve';
Param.&Type := ptString;
Param.Required := True;
Param := Tool.Parameters.Add;
Param.Name := 'quantity';
Param.Description := 'Number of units to reserve';
Param.&Type := ptNumber;
Param.Required := True;
Tool.OnExecute := ReserveStock;
EndUpdate;
end;
procedure TInventoryToolSet.GetStockLevel(Sender: TObject; Args: TJSONObject;
var Result: string);
begin
Result := IntToStr(LookupStock(Args.GetValue<string>('article_code')));
end;
procedure TInventoryToolSet.ReserveStock(Sender: TObject; Args: TJSONObject;
var Result: string);
begin
Result := ReserveUnits(Args.GetValue<string>('article_code'),
Args.GetValue<Integer>('quantity'));
end;
procedure TForm1.AttachInventoryTools;
begin
{ Setting AI registers the set. Its tools are merged with the component's
own Tools collection on every Execute call. }
FInventory := TInventoryToolSet.Create(Self);
FInventory.AI := AI;
end;
TTMSMCPCloudAIToolSet is a
component with its own Tools collection. Override the protected DefineTools
method and declare the tools there; it is called from the constructor, so a set
is fully populated the moment it is created. Wrap the declarations in
BeginUpdate / EndUpdate — EndUpdate records each tool's OnExecute
handler so it can be reattached before every request.
Setting the AI property registers the set with a TTMSMCPCloudAI. On each
Execute the component merges its own Tools with the tools of every
registered set, so both sources are advertised together. Setting AI to nil
unregisters the set, and so does freeing the component it points at.
Cloud AI Tool Sets ships ready-made descendants for logging, file system access, dataset queries, and email.
Parallel tool execution
When a question needs three independent lookups, one round trip per lookup is three times the latency.
Settings.ParallelToolExecution := True lets the model request several tool
calls in a single turn where the service supports it — currently OpenAI, Claude,
Grok, and llama.cpp. The component executes each requested call and returns all
results together. Leave it False (the default) when your handlers touch shared
state that is not safe to interleave.
Not every service supports function calling at all. Rather than hard-coding
which do, ask: GetServices(True) — or GetActiveServices(True) for services
that also have a key — returns only the services that do.
Combining tools, a tool set, and parallel execution
A production request usually mixes hand-declared tools with a packaged set, enables parallel execution, and then reads one final answer:
procedure TForm1.RunStockAssistant(const AQuestion: string);
begin
{ 1. Hand-declared tools on the component itself. }
AI.Tools.Clear;
RegisterStockTool;
RegisterReportTool;
{ 2. A reusable tool set contributes its own tools to the same request. }
if not Assigned(FInventory) then
begin
FInventory := TInventoryToolSet.Create(Self);
FInventory.AI := AI;
end;
{ 3. Let the model call several of them in one turn where the service
supports it. Only tools with Enabled set are offered. }
AI.Settings.ParallelToolExecution := True;
{ Function calling is not offered by every service - ask for the list of
services that support it rather than assuming. }
if AI.GetActiveServices(True).IndexOf('Claude') >= 0 then
AI.Service := aiClaude;
AI.SystemRole.Text :=
'Use the supplied tools to answer stock questions. Never guess a number.';
AI.Context.Text := AQuestion;
{ 4. The final answer - after all tool round trips - arrives in OnExecuted. }
AI.OnExecuted := AIExecuted;
memoLog.Lines.Add(Format('Offering %d tools', [AI.Tools.ActiveCount]));
AI.Execute;
end;
OnExecuted fires once, after the whole exchange — including every tool round
trip — has completed. The intermediate tool calls do not surface there; they
surface in your OnExecute handlers.
Common mistakes
- A vague
Description. The model chooses tools from the description alone. "Handles stock" gets called for the wrong questions; "Returns the number of units currently in stock for an article" does not. - Confusing
PropertieswithArrayProperties.Propertiesdescribes an object's fields;ArrayPropertiesdescribes one element of an array. Filling in the wrong one produces a schema the model cannot satisfy. - Leaving
Requiredat its default for optional arguments. It defaults toTrue, so an argument you meant to be optional will be invented rather than omitted. - Freeing what
Argshands you. TheTJSONObjectand any nestedTJSONObject/TJSONArrayread out of it are owned by the framework. - Declaring tools but calling a service that has none. Perplexity, for
instance, is excluded from
GetServices(True). Check the capability instead of assuming it. - Declaring a tool set's tools outside
BeginUpdate/EndUpdate.EndUpdateis what records the handlers for reattachment; without it a tool can reach the model with noOnExecutebehind it. - Blocking inside
OnExecute. The model is waiting on that result. Keep handlers short and deterministic; long work belongs behind a job the tool starts and a second tool queries.
See also
- Chat and prompting — models, settings, and the response
- Files, assistants, speech, and transcription
- Cloud AI Tool Sets — ready-made tool sets
TTMSMCPCloudAITool,TTMSMCPCloudAITools,TTMSMCPCloudAIParameter,TTMSMCPCloudAIToolSet