Fare una domanda a un modello linguistico è la parte facile. Un agente va oltre. Il modello decide quale delle tue funzioni chiamare, la tua applicazione le esegue, i risultati tornano al modello e il ciclo continua finché il modello non ha una risposta. Scrivere quel ciclo è solo l'inizio. Un'applicazione distribuita deve anche tenere il modello lontano da ciò che non deve toccare, mantenere i dati personali sulla macchina, sopravvivere al giorno in cui un provider ritira un modello e sapere quanto costa ogni utente.
sgcWebSockets 2026.11 aggiunge ai componenti sgcAI tutto ciò che serve, per Delphi da 7 a 13:
TsgcAIAgentesegue il ciclo degli strumenti su tutti e sette i provider diTsgcAIChat: OpenAI, Anthropic, Gemini, DeepSeek, Ollama, Grok e Mistral. I suoi strumenti sono i tuoi metodi Delphi e gli strumenti dei tuoi server MCP, e chiede conferma prima che uno strumento modifichi dei dati.TsgcAIGuardrailsdecide quali strumenti possono essere eseguiti, oscura i dati personali prima della richiesta e li ripristina nella risposta, e controlla le risposte strutturate rispetto a uno schema JSON.- Il pinning degli strumenti MCP blocca uno strumento del server la cui definizione cambia dopo che ti sei fidato.
- Gli alias dei modelli e il fallback tra provider permettono a un exe distribuito di continuare a rispondere quando un modello viene ritirato o un provider è fuori servizio.
TsgcAIUsageMeterconta i token, ne calcola il costo, applica i budget e invia ogni richiesta a OpenTelemetry.
Il ciclo dell'agente in un solo componente
Dai all'agente un TsgcAIChat configurato, descrivi uno strumento e chiama Run. L'agente invia il prompt con le definizioni degli strumenti, esegue ogni strumento richiesto dal modello, rimanda indietro i risultati e ripete finché il modello non risponde in testo semplice. Run restituisce quella risposta.
oChat := TsgcAIChat.Create(nil);
oChat.Provider := aicpAnthropic;
oChat.ChatOptions.ApiKey := 'API_KEY';
oChat.ChatOptions.Model := 'claude-opus-5-5';
oAgent := TsgcAIAgent.Create(nil);
oAgent.Chat := oChat;
oTool := oAgent.Tools.Add('get_weather', 'Current weather of a city');
oTool.AddParam('city', aatpString, 'City name');
oTool.ReadOnly := True;
oTool.OnExecute := DoGetWeather;
Memo1.Lines.Add(oAgent.Run('Do I need an umbrella in Paris today?'));
Lo strumento riceve i suoi argomenti come l'oggetto JSON scritto dal modello e restituisce il risultato come testo, di solito JSON:
procedure TForm1.DoGetWeather(Sender: TObject; const aArguments: string;
var aResult: string);
begin
// aArguments is a JSON object, for example {"city":"Paris"}
aResult := '{"city":"Paris","celsius":18,"sky":"rain"}';
end;
L'agente lavora sopra TsgcAIChat.ChatWithTools, che ora supporta la chiamata di strumenti su ogni provider. Passare da un modello ospitato a un modello Ollama locale significa cambiare Provider e Model, e gli strumenti restano gli stessi. Se preferisci eseguire il ciclo da solo, anche ChatWithTools e AddToolResult sono pubblici.
Controlla il ciclo
Options.MaxSteps limita il numero di richieste al modello in un'esecuzione, Cancel ferma un'esecuzione da un pulsante, da un timer o da un altro thread, e StopReason ti dice perché l'esecuzione è finita. Quando una risposta viene interrotta dal limite di token, l'esecuzione si ferma con aasrMaxTokens e le chiamate agli strumenti di quella risposta non vengono eseguite, perché i loro argomenti potrebbero essere incompleti. OnStep segnala ogni passaggio mentre accade, così l'utente può vedere cosa sta facendo l'agente.
oAgent.Options.MaxSteps := 5;
oAgent.OnStep := oAgentStep;
vAnswer := oAgent.Run('Plan a three day trip to Rome');
case oAgent.StopReason of
aasrCompleted:
Memo1.Lines.Add(vAnswer);
aasrMaxSteps:
Memo1.Lines.Add('Stopped after 5 model requests');
aasrMaxTokens:
Memo1.Lines.Add('Cut by the token limit (' + oChat.LastFinishReason + ')');
aasrCancelled:
Memo1.Lines.Add('Cancelled');
end;
procedure TForm1.oAgentStep(Sender: TObject; const aStep: TsgcAIAgentStep);
begin
case aStep.Kind of
aaskToolCall:
Memo1.Lines.Add('Calling ' + aStep.ToolName + ' ' + aStep.Arguments);
aaskToolResult, aaskToolDenied:
Memo1.Lines.Add(aStep.ToolName + ': ' + aStep.Content);
aaskFinal:
Memo1.Lines.Add('Answer: ' + aStep.Content);
end;
end;
Uno strumento che fallisce non termina l'esecuzione. La sua eccezione torna al modello come testo di errore, e il modello può provare qualcos'altro. Un errore della chat stessa, per esempio un guasto di rete, termina l'esecuzione con aasrError e l'eccezione arriva al tuo codice. OnUsage riporta i token di ogni richiesta al modello dell'esecuzione.
I tuoi metodi Delphi come strumenti
Uno strumento è un nome, una descrizione, parametri tipizzati e un gestore, come sopra. Quando hai già un metodo che fa il lavoro, AddMethod lo pubblica così com'è. Il nome dello strumento è il nome del metodo, e i parametri, i loro tipi e il risultato vengono letti con l'RTTI esteso, quindi questa funzione richiede Delphi 2010 o successivo.
function TForm1.Forecast(const aCity: string; aDays: Integer): string;
begin
Result := Format('%s: sunny for %d days', [aCity, aDays]);
end;
oAgent.Tools.AddMethod(Self, 'Forecast',
'Weather forecast of a city for the next days');
Per uno strumento con un input più ricco, imposta InputSchema con il tuo schema JSON. Enabled toglie uno strumento dalle esecuzioni successive senza rimuoverlo.
Gli strumenti dei tuoi server MCP
Aggiungi uno o più client MCP a MCPClients e l'agente offre gli strumenti di quei server accanto ai tuoi. Vengono elencati una sola volta per esecuzione e chiamati tramite il client quando il modello li richiede. Uno strumento che il server contrassegna con readOnlyHint conta come di sola lettura, e quando uno strumento locale e uno del server hanno lo stesso nome, vince quello locale.
oMCP := TsgcWSAPIClient_MCP.Create(nil);
oMCP.MCPOptions.HttpOptions.URL := 'http://127.0.0.1:8080/mcp';
oAgent.MCPClients.Add(oMCP);
Memo1.Lines.Add(oAgent.Run('List the support tickets opened today'));
Approvazione prima che qualcosa cambi
Ogni strumento ha un flag ReadOnly. Gli strumenti di sola lettura vengono eseguiti appena il modello li richiede. Uno strumento che modifica dati, invia una mail o spende denaro resta non di sola lettura, e l'agente interroga OnToolApproval prima di eseguirlo, con il nome dello strumento e gli argomenti esatti scelti dal modello.
oTool := oAgent.Tools.Add('send_reminder', 'Sends a payment reminder');
oTool.AddParam('invoice', aatpString, 'Invoice number');
oTool.OnExecute := DoSendReminder; // ReadOnly is False: it changes data
oAgent.OnToolApproval := oAgentToolApproval;
procedure TForm1.oAgentToolApproval(Sender: TObject; const aToolName,
aArguments: string; var aApproved: Boolean);
begin
aApproved := MessageDlg('Run ' + aToolName + ' with ' + aArguments + '?',
mtConfirmation, [mbYes, mbNo], 0) = mrYes;
end;
Uno strumento rifiutato non è un errore. Al modello viene detto che lo strumento non è stato approvato, e può rispondere all'utente senza di esso. Con i guardrails assegnati, come nella sezione successiva, uno strumento che richiede approvazione e non trova alcun gestore viene rifiutato, così un evento dimenticato non lascia mai passare uno strumento di scrittura.
Guardrails
TsgcAIGuardrails è un componente separato che assegni alla chat, così le stesse regole valgono per l'agente e per le semplici richieste di chat. Ha tre compiti.
- Regole sugli strumenti.
AllowedToolseDeniedToolsaccettano nomi con caratteri jolly*, e un divieto vince sempre. Uno strumento non consentito non viene mai offerto al modello.ToolApprovalsceglie quali strumenti richiedono approvazione: nessuno, quelli che non sono di sola lettura, oppure tutti. - Dati personali. Con
PII.Enabled, indirizzi e-mail, numeri di telefono, numeri di carta, IBAN, indirizzi IP e i tuoi pattern vengono sostituiti da segnaposto prima che la richiesta lasci l'applicazione, e gli originali vengono rimessi nella risposta. - Schema della risposta. Con
Schema.Enabled, una risposta strutturata che non corrisponde allo schema JSON viene rifiutata con il percorso dell'errore, per esempio$.price: expected number, eOnSchemaViolationte lo segnala.
oGuard := TsgcAIGuardrails.Create(nil);
// tools: reading is allowed, deleting never
oGuard.AllowedTools.Add('get_*');
oGuard.AllowedTools.Add('list_*');
oGuard.AllowedTools.Add('send_reminder');
oGuard.DeniedTools.Add('*delete*');
oGuard.ToolApproval := aigaNotReadOnly;
// personal data never leaves the application
oGuard.PII.Enabled := True;
oGuard.PII.CustomPatterns.Add('EmployeeId=EMP-####');
// structured answers must match the schema
oGuard.Schema.Enabled := True;
oGuard.Schema.JSONSchema := '{"type":"object","properties":' +
'{"price":{"type":"number","minimum":0}},"required":["price"]}';
oChat.Guardrails := oGuard;
Puoi richiamare tu stesso l'oscuramento per vedere cosa riceverà il provider e cosa riottiene la tua applicazione:
vText := oGuard.Redact('Mail bob@example.com, call +34 600 111 222, ' +
'employee EMP-1234');
// Mail [EMAIL_1], call [PHONE_1], employee [EMPLOYEEID_1]
vText := oGuard.Restore(vText);
// Mail bob@example.com, call +34 600 111 222, employee EMP-1234
Fissa gli strumenti dei tuoi server MCP
La descrizione e lo schema di input di uno strumento MCP vengono letti dal modello come istruzioni. Un server che li cambia dopo che ti sei fidato, perché è stato compromesso o perché il suo proprietario li ha modificati, può nascondere nuove istruzioni in uno strumento che già usi. Questo si chiama tool poisoning.
Con il pinning degli strumenti, TsgcWSAPIClient_MCP conserva un'impronta SHA-256 di nome, descrizione, schema di input e annotazioni di ogni strumento la prima volta che lo elenca. Quando un elenco successivo restituisce una definizione diversa, lo strumento viene bloccato. Viene rimosso dall'elenco, l'agente non lo offre mai, e una chiamata a esso solleva un'eccezione prima che venga inviato qualsiasi cosa. OnMCPToolChanged ti permette di accettare la modifica o di tenere lo strumento bloccato per una verifica, e gli altri strumenti del server continuano a funzionare.
oMCP.MCPOptions.ToolPinning.Enabled := True;
oMCP.MCPOptions.ToolPinning.FileName := 'mcp_pins.json';
oMCP.OnMCPToolChanged := oMCPMCPToolChanged;
procedure TForm1.oMCPMCPToolChanged(Sender: TObject; const aToolName,
aOldHash, aNewHash: string; var aAccept: Boolean);
begin
// the definition of a trusted tool changed: keep it blocked
// until somebody reviews it, then call oMCP.PinTool(aToolName)
Memo1.Lines.Add('Tool ' + aToolName + ' changed, blocked for review');
aAccept := False;
end;
Un modello ritirato non fermerà un exe distribuito
I nomi dei modelli cambiano più in fretta delle applicazioni installate. Un nome di modello compilato in un exe smette di funzionare il giorno in cui il provider lo ritira, e ogni cliente ha bisogno di una nuova build. Con gli alias dei modelli, il codice chiede smart o fast, e l'alias decide il modello e, se vuoi, il provider. L'elenco si carica da un file, da JSON o da un URL, quindi passare a un nuovo modello è una modifica sul tuo server, non una nuova versione.
oChat.ModelAliases.Add('fast=gpt-6-luna');
oChat.ModelAliases.Add('smart=anthropic:claude-opus-5-5');
oChat.ProviderApiKeys.Add('anthropic=ANTHROPIC_KEY');
oChat.ChatOptions.Model := 'smart';
// or keep the list on your server and change it without a new build
oChat.LoadModelAliasesFromURL('https://config.example.com/ai/models.json');
I fallback coprono le interruzioni. Quando il provider fallisce con un errore di connessione, un HTTP 5xx, un HTTP 429 o un modello che non conosce, la stessa richiesta passa al provider e al modello successivi dell'elenco. La cronologia della conversazione, gli strumenti e le risposte JSON vengono mantenuti, e OnFallback ti segnala che è successo. Un modello Ollama locale è un buon ultimo rimedio.
oChat.Provider := aicpOpenAI;
oChat.ChatOptions.ApiKey := 'OPENAI_KEY';
oChat.ChatOptions.Model := 'gpt-6-luna';
oFallback := oChat.Fallbacks.Add;
oFallback.Provider := aicpGemini;
oFallback.Model := 'gemini-3-flash';
oFallback.ApiKey := 'GEMINI_KEY';
oFallback := oChat.Fallbacks.Add;
oFallback.Provider := aicpOllama;
oFallback.Model := 'llama3.1';
oChat.OnFallback := oChatFallback;
procedure TForm1.oChatFallback(Sender: TObject; const aFromProvider,
aFromModel, aToProvider, aToModel, aError: string);
begin
Memo1.Lines.Add(aFromProvider + ' ' + aFromModel + ' failed (' + aError +
'), trying ' + aToProvider + ' ' + aToModel);
end;
Sai quanto costa ogni richiesta
TsgcAIUsageMeter registra i token di ogni richiesta, compresi i token di input in cache, ne calcola il costo con il listino che gli dai e tiene i totali per utente e per sessione. Assegnalo alla chat e imposta UserId e SessionId. Poiché il contatore si trova sulla chat, viene conteggiata anche ogni richiesta al modello di un'esecuzione dell'agente.
oMeter := TsgcAIUsageMeter.Create(nil);
// example prices per million tokens, use the ones of your contract
oPrice := oMeter.Prices.Add;
oPrice.Provider := 'openai';
oPrice.Model := 'gpt-6*';
oPrice.InputPerMillion := 1.25;
oPrice.OutputPerMillion := 10;
oPrice.CachedPerMillion := 0.125;
oChat.UserId := 'user-1';
oChat.SessionId := 's-42';
oChat.UsageMeter := oMeter;
oChat.Chat('Hello');
Memo1.Lines.Add(Format('user-1 spent %.6f %s and %d tokens',
[oMeter.TotalCost('user-1'), oMeter.Currency, oMeter.TotalTokens('user-1')]));
Il listino associa provider e modello con caratteri jolly e può essere caricato da JSON o da un file, quindi anche una variazione di prezzo non richiede una nuova build. I budget trasformano il contatore in un limite. Un budget copre l'intera applicazione, ogni utente o ogni sessione, per un giorno, un mese o in totale, in denaro o in token. Quando un budget è esaurito, la richiesta successiva viene rifiutata prima di essere inviata, quindi un budget può anche fermare a metà l'esecuzione di un agente.
// every user can spend 2 USD a day
oBudget := oMeter.Budgets.Add;
oBudget.Scope := aubsUser;
oBudget.Period := aubpDay;
oBudget.MaxCost := 2;
// the whole application can spend 500 USD a month
oBudget := oMeter.Budgets.Add;
oBudget.Scope := aubsGlobal;
oBudget.Period := aubpMonth;
oBudget.MaxCost := 500;
try
Memo1.Lines.Add(oAgent.Run('Remind every customer with an unpaid invoice'));
except
on E: EsgcAIChat do
// Usage budget "each user, day" for user-1 exceeded: ...
Memo1.Lines.Add(E.Message);
end;
Imposta BlockWhenExceeded su False per un budget che si limita a far scattare OnBudgetExceeded come avviso. Per i report, i record vanno in un file CSV o in qualsiasi TDataSet. Con OpenTelemetry attivo, ogni richiesta diventa uno span che segue le convenzioni semantiche GenAI e va in background al tuo collector OTLP/HTTP, senza rallentare la chat.
oMeter.OpenTelemetry.Endpoint := 'http://localhost:4318';
oMeter.OpenTelemetry.ServiceName := 'invoice-assistant';
oMeter.OpenTelemetry.Enabled := True;
oMeter.SaveToCSV('usage.csv');
La demo: un assistente per le fatture
La nuova demo Delphi in Demos\15.AI\02.Applications\11.AIAgent è un assistente per un elenco di fatture non pagate. L'agente ha tre strumenti. list_unpaid_invoices e get_invoice sono di sola lettura, mentre send_reminder modifica dati e chiede prima la tua approvazione. L'agente gira in un thread in background, quindi la finestra resta reattiva mentre lavora. La demo ti permette anche di provare il resto di questo articolo: strumenti negati e oscuramento dei dati personali, un budget per sessione con un contatore live di token e costi e un'esportazione CSV, alias dei modelli, un provider di fallback e un server MCP i cui strumenti sono fissati.
Documentazione che il tuo agente di codifica può leggere
Un agente di codifica scrive buon codice solo per le librerie che conosce. Ogni componente di sgcAI, sgcHTML, sgcCrypto e degli altri pack di sgcWebSockets ha ora una propria pagina Markdown per i modelli linguistici, 224 pagine in tutto. Ogni pagina dice cosa fa il componente e rimanda alla sua pagina di prodotto, e la maggior parte elenca le proprietà, i metodi e gli eventi principali. La pagina di TsgcAIAgent, ai-agent.md, contiene anche un esempio d'uso completo. llms.txt indirizza Claude Code, Copilot, Cursor e altri agenti a queste pagine, così l'agente legge l'API reale prima di scrivere una riga.
Anche le skill per agenti di sgcWebSockets imparano i nuovi componenti. La skill IA di 2026.11.0 contiene schemi pronti per il ciclo dell'agente, l'approvazione degli strumenti, i guardrails, gli alias dei modelli con fallback e il contatore d'uso, e ogni schema deriva da codice che compila. Skill per agenti IA per Delphi, C++Builder e .NET spiega come installarle.
Disponibilità
TsgcAIAgent, TsgcAIGuardrails, TsgcAIUsageMeter, gli alias dei modelli e i fallback di TsgcAIChat e il pinning degli strumenti MCP arrivano in sgcWebSockets 2026.11.0, per Delphi da 7 a 13 e C++Builder. Fanno parte dei componenti sgcAI, inclusi nelle edizioni Enterprise e All-Access e nel prodotto autonomo sgcAI. La guida documenta ogni proprietà, metodo ed evento, con pagine sul ciclo dell'agente, sugli strumenti, sugli alias e i fallback, sull'oscuramento dei dati personali, sulla validazione dello schema e sul pinning degli strumenti.
Per saperne di più, consulta le pagine dei componenti TsgcAIAgent, TsgcAIGuardrails e TsgcAIUsageMeter.
Leggi anche
- Costruire un agente AI in Delphi: function calling e uso degli strumenti MCP, il ciclo dell'agente spiegato passo dopo passo
- Client MCP per Delphi
- OpenAI vs Anthropic vs Gemini in Delphi: quale API AI dovresti usare?
Domande o suggerimenti? Contattaci. Riceverai una risposta da chi ha scritto il codice.
