Table of Contents

The four shipped tool sets

TMS AI Studio ships four tool sets: logging, file system access, dataset access, and email. Between them they cover the four things an application is most often asked to let a model do — say something, touch the disk, read the data, and send a message — and each one is a single component with no declarations to write. What they do not do is decide policy: none of them restricts a path, a table, or a recipient, and none asks for confirmation before it writes. This guide lists every tool of every set with its exact arguments and what it returns to the model, the component properties each set reads while its tools run, and the ordering constraints that decide whether a conversation succeeds or fails halfway through.

Logging with the logger set

Reach for TTMSMCPCloudAILogger when the model should record or surface a result rather than only return it — an audit trail of what an assistant decided, or a message the user sees immediately. It declares three tools:

Tool Arguments Does
ShowDialog result (string, required) Shows the text in a message box.
ShowInConsole result (string, required) Writes the text to the framework log.
LogToFile result (string, required), logfile (string, optional) Appends the text to a file, prefixed with a yyyy-mm-dd hh:nn:ss timestamp.

LogFileName on the component is the fallback LogToFile uses when the model omits the optional logfile argument. With neither supplied the tool raises No log file specified, so set the property even when you expect the model to name a file:

procedure TForm1.AttachLogger;
begin
  FLogger := TTMSMCPCloudAILogger.Create(Self);

  { LogToFile falls back to this name whenever the model omits its optional
    logfile argument. Leave it empty and a call without that argument
    raises 'No log file specified'. }
  FLogger.LogFileName := TPath.Combine(TPath.GetDocumentsPath, 'assistant.log');

  FLogger.AI := AI;
end;

procedure TForm1.btnReviewClick(Sender: TObject);
begin
  if AI.Busy then
    Exit;

  { ShowDialog puts the text in a message box, ShowInConsole writes it to
    the framework log, LogToFile appends it with a timestamp. Say which one
    you want - the model picks otherwise. }
  AI.SystemRole.Text :=
    'Review the text for factual errors. Report every finding with the ' +
    'LogToFile tool, one call per finding, and show a short summary with ' +
    'the ShowDialog tool when you are done.';
  AI.Context.Text := memoInput.Lines.Text;
  AI.Execute;
end;

ShowDialog is modal, which matters more than it looks: it blocks inside a tool call, in the middle of a request the model is waiting on. It is useful for a desktop assistant and unsuitable for anything unattended — a service, a batch run, or a session the user has walked away from. ShowDialog and ShowInConsole are compiled in for VCL and FMX applications; in a console-style build without a UI framework, ShowDialog does nothing while ShowInConsole and LogToFile keep working.

File system access

TTMSMCPCloudAIFileSystem is the set to attach when the model has to find something — read a report, inspect a folder of exports, reorganise files by their contents. It needs no configuration at all: every tool takes the paths it works on as arguments.

Tool Arguments Returns
GetFiles Folder (string, required), Mask (string, optional), Details (boolean, optional) The files in one folder. With Details, each entry also carries its size and last-modified date.
GetFolders Folder (string, required), Mask (string, optional) The subfolders of one folder.
ReadTextFile Filename (string, required) The whole file as text.
WriteTextFile Filename (string, required), Content (string, required) Replaces the file's contents.
AppendTextFile Filename (string, required), Content (string, required) Adds to the end of an existing file.
CreateFolder Folder (string, required), Foldername (string, required) Creates a subfolder inside Folder.
CopyFile Source, Target (strings, required), Overwrite (boolean, optional) Copies a file.
MoveFile Source, Target (strings, required), Overwrite (boolean, optional) Moves a file.

Both listing tools search the named folder only — they do not recurse — and an empty Folder argument resolves to the process's current directory, which is rarely what anyone meant. The mask is an ordinary file mask (*.log), and the default is every file.

procedure TForm1.AttachFileSystem;
begin
  { The file system set has no properties to configure: its eight tools
    take the folder and file names they work on as arguments. }
  FFiles := TTMSMCPCloudAIFileSystem.Create(Self);
  FFiles.AI := AI;
end;

procedure TForm1.btnTidyClick(Sender: TObject);
begin
  if AI.Busy then
    Exit;

  { Nothing in the tool set restricts the paths the model may name, so the
    boundary has to come from the prompt - or from disabling the writing
    tools outright. }
  AI.SystemRole.Text :=
    'You may only work inside ' + FWorkRoot + ' and its subfolders. ' +
    'Use GetFiles and GetFolders to look around, ReadTextFile to inspect ' +
    'a file, and CreateFolder plus MoveFile to reorganise. Never delete ' +
    'anything and never leave that folder.';
  AI.Context.Text :=
    'Group the invoices in this folder into one subfolder per year, ' +
    'based on the date inside each file.';
  AI.Execute;
end;

WriteTextFile replaces rather than appends, and neither it nor MoveFile asks before overwriting the target when the model passes Overwrite. When the model only needs to read, clear Enabled on the five writing tools as shown in Attaching tool sets — that is a real boundary, whereas an instruction in the system role is only a strong suggestion.

Dataset access

TTMSMCPCloudAIDataSet reaches an open dataset through the standard DataSource property, so it works with any TDataSet descendant the application already has on a form. Use it when the answer lives in the data rather than in a document, and when you would otherwise be writing one hand-declared tool per query.

Tool Arguments Does
GetFields Returns every field with its name, size, type, and kind.
GetTableName Returns the dataset's table name.
GetRecords Fields (string, optional) Returns every record. Fields is a comma-separated list that narrows the columns.
GetRecord Fields (string, optional) The same, for the current record only.
First, Last, Next, Previous Move the active record.
Move Number (number, required) Moves the active record forward or backward by that many rows.
AddRecord FieldValues (array of {Name, Value}, required) Inserts a record with those field values.
ModifyRecord FieldValues (array of {Name, Value}, required) Updates the current record.
LocateRecord FieldValues (array of {Name, Value}, required) Positions the dataset on the first matching record, case-insensitively.
procedure TForm1.AttachDataSetTools;
begin
  { The tools reach the dataset through a TDataSource, not directly. Every
    one of them refuses to run while the dataset is closed. }
  FDQuery.Open;

  FDataTools := TTMSMCPCloudAIDataSet.Create(Self);
  FDataTools.DataSource := dsCustomers;
  FDataTools.AI := AI;
end;

procedure TForm1.btnAnalyseClick(Sender: TObject);
begin
  if AI.Busy then
    Exit;

  { GetFields and GetTableName let the model learn the shape of the data
    before it asks for rows, so it is worth naming them explicitly. }
  AI.SystemRole.Text :=
    'Call GetFields first so you know the column names, then use ' +
    'GetRecords with a Fields list to fetch only the columns you need. ' +
    'Ask before changing any record.';
  AI.Context.Text :=
    'Which customers in this table have no country set, and what would ' +
    'you suggest filling in for each?';
  AI.Execute;
end;

procedure TForm1.btnCorrectClick(Sender: TObject);
begin
  if AI.Busy then
    Exit;

  { LocateRecord positions the dataset, ModifyRecord edits the record that
    is current after it. Ordering matters: ask for both in one instruction
    so the model does not modify whichever row happened to be active. }
  AI.SystemRole.Text :=
    'To correct a row, call LocateRecord with the field values that ' +
    'identify it, then ModifyRecord with the corrected field values.';
  AI.Context.Text := memoInstruction.Lines.Text;
  AI.Execute;
end;

Four details decide whether a run behaves. Every tool checks the dataset first and answers No dataset assigned or dataset not active when DataSource, its DataSet, or the dataset's Active state is missing — so open the dataset before the request, not in response to a failed one. GetRecords walks the whole dataset and returns each field's display text, so formatted values (dates, currency, boolean fields) arrive the way they would appear on screen rather than as raw values. It also restores the original record position afterwards, so it is safe to call between navigation steps. And ModifyRecord acts on wherever the dataset currently is, which is why it is only meaningful immediately after LocateRecord — say so in the system role, or the model will happily modify whichever row was already current.

Every value in a FieldValues entry is a string on the wire. Numeric and date fields are converted on assignment, so a value the model phrases loosely ("next Friday") fails at the field rather than being interpreted.

Email

TTMSMCPCloudAIEmail sends over SMTP and reads over POP3, using Indy clients it owns internally. Attach it when the model should triage a mailbox or send what it has produced.

Tool Arguments Does
SendEmail EmailTo, EmailSubject, EmailBody (strings, required) Connects to the SMTP host and sends one message.
GetCountEmail Returns the number of messages waiting, opening the POP3 connection if needed.
RetrieveEmail Index (number, required) Returns the sender, subject, and body of one message as JSON.

The set reads its server settings from published properties. Note the two spellings inherited from the original API — SMPTPort and SMPTUserName:

Property Used for
SMTPHost, SMPTPort Outgoing server and port.
SMPTUserName, SMTPPassword Outgoing credentials.
SMTPSendFrom The From address on every message the model sends.
PopHost, PopPort Incoming server and port.
PopUserName, PopPassword Incoming credentials.
PopUseSSL Switches the POP3 connection to explicit TLS.
procedure TForm1.AttachEmailTools;
begin
  FMail := TTMSMCPCloudAIEmail.Create(Self);

  { Outgoing. The credentials come from the application configuration -
    never from a literal in the source. }
  FMail.SMTPHost := Config.ReadString('mail', 'smtp_host', '');
  FMail.SMPTPort := Config.ReadInteger('mail', 'smtp_port', 587);
  FMail.SMPTUserName := Config.ReadString('mail', 'smtp_user', '');
  FMail.SMTPPassword := Config.ReadString('mail', 'smtp_password', '');
  FMail.SMTPSendFrom := Config.ReadString('mail', 'send_from', '');

  { Incoming. }
  FMail.PopHost := Config.ReadString('mail', 'pop_host', '');
  FMail.PopPort := Config.ReadInteger('mail', 'pop_port', 995);
  FMail.PopUserName := Config.ReadString('mail', 'pop_user', '');
  FMail.PopPassword := Config.ReadString('mail', 'pop_password', '');
  FMail.PopUseSSL := True;

  FMail.AI := AI;
end;

procedure TForm1.btnTriageClick(Sender: TObject);
begin
  if AI.Busy then
    Exit;

  { GetCountEmail opens the POP3 connection if it is not open yet, and
    RetrieveEmail needs that connection, so the count has to come first.
    RetrieveEmail counts from 1, not from 0. }
  AI.SystemRole.Text :=
    'Call GetCountEmail first, then RetrieveEmail for each message from ' +
    'index 1 upwards. Summarise what arrived, and only send a reply with ' +
    'SendEmail when the user asks you to.';
  AI.Context.Text := 'What came in today and what needs an answer?';
  AI.Execute;
end;

procedure TForm1.CloseMailConnection;
begin
  { The POP3 connection stays open between requests. Close it when the
    conversation ends rather than leaving the session on the server. }
  FMail.DisconnectFromServer;
end;

Two ordering rules are worth stating in the system role. RetrieveEmail requires an open POP3 connection and refuses when there is none, while GetCountEmail opens one as a side effect — so the count has to come first in any conversation that reads mail. And POP3 indexes start at 1: asking for index 0, or for one past the count, answers with an error string rather than a message.

Beyond the tools, the component exposes the same operations to your own code: ConnectToServer, DisconnectFromServer, GetEmailCount, RetrieveEmail returning a TEmailInfo record, and RetrieveEmailMessage returning a full Indy message you own and must free. The connection is kept open between requests, so call DisconnectFromServer when the conversation ends instead of leaving a session on the server.

Combining the dataset, email, and logger sets

Several sets can serve one component, and that is where tool sets earn their keep: a request that reads the data, acts on it, and records what it did needs no tool declaration at all, only three components and one clear instruction.

procedure TForm1.RunOverdueRun;
begin
  { 1. The dataset set supplies the data. It needs an open dataset. }
  if not FDQuery.Active then
    FDQuery.Open;

  if not Assigned(FDataTools) then
  begin
    FDataTools := TTMSMCPCloudAIDataSet.Create(Self);
    FDataTools.DataSource := dsInvoices;
    FDataTools.AI := AI;
  end;

  { 2. The email set sends the reminders. }
  if not Assigned(FMail) then
  begin
    FMail := TTMSMCPCloudAIEmail.Create(Self);
    FMail.SMTPHost := Config.ReadString('mail', 'smtp_host', '');
    FMail.SMPTPort := Config.ReadInteger('mail', 'smtp_port', 587);
    FMail.SMPTUserName := Config.ReadString('mail', 'smtp_user', '');
    FMail.SMTPPassword := Config.ReadString('mail', 'smtp_password', '');
    FMail.SMTPSendFrom := Config.ReadString('mail', 'send_from', '');
    FMail.AI := AI;
  end;

  { 3. The logger set records every reminder that went out. }
  if not Assigned(FLogger) then
  begin
    FLogger := TTMSMCPCloudAILogger.Create(Self);
    FLogger.LogFileName := TPath.Combine(FWorkRoot, 'reminders.log');
    FLogger.AI := AI;
  end;

  { All three sets are merged into one tool list on Execute, so the model
    sees the dataset, mail, and logging tools side by side. Spell out the
    order you expect - the tools themselves enforce none. }
  AI.SystemRole.Text :=
    'Call GetFields, then GetRecords to find the invoices whose due date ' +
    'has passed. Send one short reminder per customer with SendEmail, and ' +
    'record each one with LogToFile. Send nothing to a customer twice.';
  AI.Context.Text := 'Run the overdue reminders for today.';
  AI.OnExecuted := AIExecuted;
  AI.Execute;
end;

The tools of all three sets are merged into a single list on Execute, so the model sees GetRecords, SendEmail, and LogToFile side by side and has to be told the order you expect. Nothing in the sets sequences them, and nothing prevents the model from sending before it has finished reading.

Common mistakes

  • Leaving LogFileName empty. LogToFile raises No log file specified when neither the property nor the model's optional logfile argument is present. Set the property as a fallback.
  • Using ShowDialog in an unattended process. It is a modal message box raised inside a tool call, so the request blocks until someone dismisses it.
  • Calling a dataset tool against a closed dataset. Every one of the twelve answers No dataset assigned or dataset not active. Open the dataset before the request.
  • Expecting ModifyRecord to find its own row. It edits the current record. Pair it with LocateRecord in the same instruction, or the model edits whichever record happened to be active.
  • Reading raw values out of GetRecords. It returns each field's display text, so a currency or date field arrives formatted. Say so in the prompt when the model has to compare values.
  • Retrieving mail before counting it. RetrieveEmail needs an open POP3 connection; GetCountEmail is what opens it. Ask for the count first.
  • Treating the mail index as zero-based. POP3 messages are numbered from 1.
  • Spelling the mail properties the way they read. They are SMPTPort and SMPTUserName, not SMTPPort and SMTPUserName.
  • Expecting the sets to enforce a boundary. No shipped tool limits a path, a table, or a recipient. Withhold tools with Enabled and state the limits in the system role.

See also