Maak je Delphi-app klaar voor agents

· Componenten
Delphi-app klaar voor agents: TsgcAIAgent

Een taalmodel een vraag stellen is het makkelijke deel. Een agent gaat verder. Het model bepaalt welke van je functies het aanroept, je toepassing voert ze uit, de resultaten gaan terug naar het model en de loop gaat door totdat het model een antwoord heeft. Die loop schrijven is pas het begin. Een toepassing in productie moet het model ook weghouden van wat het niet mag aanraken, persoonsgegevens op de machine houden, de dag overleven waarop een provider een model buiten gebruik stelt, en weten wat elke gebruiker kost.

sgcWebSockets 2026.11 voegt de onderdelen daarvoor toe aan de sgcAI-componenten, voor Delphi 7 tot 13:

De agentloop in één component

Geef de agent een geconfigureerde TsgcAIChat, beschrijf een tool en roep Run aan. De agent stuurt de prompt met de tooldefinities mee, voert elke tool uit die het model vraagt, stuurt de resultaten terug en herhaalt dit totdat het model in gewone tekst antwoordt. Run geeft dat antwoord terug.

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?'));

De tool ontvangt zijn argumenten als het JSON-object dat het model heeft geschreven, en geeft zijn resultaat terug als tekst, meestal 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;

De agent werkt bovenop TsgcAIChat.ChatWithTools, dat nu tool calling ondersteunt op elke provider. Van een gehost model overstappen op een lokaal Ollama-model is een wijziging van Provider en Model, en de tools blijven dezelfde. Wil je de loop liever zelf draaien, dan zijn ChatWithTools en AddToolResult ook publiek.

Houd de loop onder controle

Options.MaxSteps begrenst het aantal modelverzoeken in één run, Cancel stopt een run vanaf een knop, een timer of een andere thread, en StopReason vertelt waarom de run eindigde. Wordt een antwoord door de tokenlimiet afgekapt, dan stopt de run met aasrMaxTokens en worden de toolaanroepen van dat antwoord niet uitgevoerd, omdat hun argumenten onvolledig kunnen zijn. OnStep meldt elke stap terwijl die gebeurt, zodat de gebruiker kan zien wat de agent doet.

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;

Een tool die mislukt, beëindigt de run niet. Zijn exception gaat als foutmelding terug naar het model, dat iets anders kan proberen. Een fout van de chat zelf, bijvoorbeeld een netwerkstoring, beëindigt de run met aasrError en de exception bereikt je code. OnUsage meldt de tokens van elk modelverzoek van de run.

Je Delphi-methoden als tools

Een tool is een naam, een beschrijving, getypeerde parameters en een handler, zoals hierboven. Heb je al een methode die het werk doet, dan publiceert AddMethod die zoals ze is. De toolnaam is de methodenaam, en de parameters, hun typen en het resultaat worden gelezen met extended RTTI, dus deze vereist Delphi 2010 of nieuwer.

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');

Voor een tool met een rijkere invoer stel je InputSchema in op je eigen JSON-schema. Enabled haalt een tool uit de volgende runs zonder hem te verwijderen.

Tools van je MCP-servers

Voeg een of meer MCP-clients toe aan MCPClients en de agent biedt de tools van die servers aan naast je eigen tools. Ze worden één keer per run opgevraagd en via de client aangeroepen wanneer het model erom vraagt. Een tool die de server markeert met readOnlyHint telt als alleen-lezen, en als een lokale tool en een servertool dezelfde naam hebben, wint de lokale.

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'));

Goedkeuring voordat er iets verandert

Elke tool heeft een ReadOnly-vlag. Alleen-lezen tools worden uitgevoerd zodra het model erom vraagt. Een tool die gegevens wijzigt, een mail verstuurt of geld uitgeeft, blijft niet alleen-lezen, en de agent vraagt OnToolApproval voordat hij draait, met de toolnaam en de exacte argumenten die het model koos.

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;

Een geweigerde tool is geen fout. Het model hoort dat de tool niet is goedgekeurd en kan de gebruiker antwoorden zonder die tool. Met guardrails gekoppeld, zoals in de volgende sectie, wordt een tool die goedkeuring nodig heeft maar geen handler vindt geweigerd, zodat een vergeten event nooit een schrijvende tool doorlaat.

Guardrails

TsgcAIGuardrails is een apart component dat je aan de chat koppelt, zodat dezelfde regels gelden voor de agent en voor gewone chatverzoeken. Het heeft drie taken.

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;

Je kunt de maskering zelf aanroepen om te zien wat de provider ontvangt en wat je toepassing terugkrijgt:

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

Pin de tools van je MCP-servers

De beschrijving en het invoerschema van een MCP-tool worden door het model gelezen als instructies. Een server die ze wijzigt nadat je hem vertrouwde, omdat hij gecompromitteerd is of omdat de eigenaar hem heeft aangepast, kan nieuwe instructies verbergen in een tool die je al gebruikt. Dit staat bekend als tool poisoning.

Met tool pinning bewaart TsgcWSAPIClient_MCP een SHA-256-vingerafdruk van naam, beschrijving, invoerschema en annotaties van elke tool de eerste keer dat hij die opvraagt. Geeft een latere lijst een andere definitie terug, dan wordt de tool geblokkeerd. Hij wordt uit de lijst gehaald, de agent biedt hem nooit aan, en een aanroep ervan geeft een exception voordat er iets wordt verstuurd. Met OnMCPToolChanged kun je de wijziging accepteren of de tool geblokkeerd houden voor controle, en de andere tools van de server blijven werken.

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;

Een buiten gebruik gesteld model stopt een uitgeleverde exe niet

Modelnamen veranderen sneller dan geïnstalleerde toepassingen. Een modelnaam die in een exe is gecompileerd, werkt niet meer op de dag dat de provider het model buiten gebruik stelt, en elke klant heeft een nieuwe build nodig. Met modelaliassen vraagt de code om smart of fast, en de alias bepaalt het model en, als je wilt, de provider. De lijst wordt geladen uit een bestand, uit JSON of van een URL, dus overstappen op een nieuw model is een wijziging op je server, geen nieuwe release.

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');

Fallbacks vangen storingen op. Als de provider faalt met een verbindingsfout, een HTTP 5xx, een HTTP 429 of een model dat hij niet kent, gaat hetzelfde verzoek naar de volgende provider en het volgende model van de lijst. De gespreksgeschiedenis, de tools en JSON-antwoorden gaan mee, en OnFallback meldt dat het gebeurde. Een lokaal Ollama-model is een goed laatste redmiddel.

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;

Weet wat elk verzoek kost

TsgcAIUsageMeter registreert de tokens van elk verzoek, inclusief gecachete invoertokens, prijst ze met de prijslijst die je opgeeft en houdt totalen per gebruiker en per sessie bij. Koppel het aan de chat en stel UserId en SessionId in. Omdat de meter op de chat zit, wordt ook elk modelverzoek van een agentrun geteld.

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')]));

De prijslijst koppelt provider en model met jokertekens en kan uit JSON of uit een bestand worden geladen, dus ook een prijswijziging vraagt geen nieuwe build. Budgetten maken van de meter een limiet. Een budget geldt voor de hele toepassing, voor elke gebruiker of voor elke sessie, voor een dag, een maand of in totaal, in geld of in tokens. Als een budget op is, wordt het volgende verzoek geweigerd voordat het wordt verstuurd, zodat een budget ook een agentrun halverwege kan stoppen.

// 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;

Zet BlockWhenExceeded op False voor een budget dat alleen OnBudgetExceeded afvuurt als waarschuwing. Voor rapporten gaan de records naar een CSV-bestand of naar elke TDataSet. Met OpenTelemetry ingeschakeld wordt elk verzoek een span die de GenAI semantic conventions volgt en op de achtergrond naar je OTLP/HTTP-collector gaat, zonder de chat te vertragen.

oMeter.OpenTelemetry.Endpoint := 'http://localhost:4318';
oMeter.OpenTelemetry.ServiceName := 'invoice-assistant';
oMeter.OpenTelemetry.Enabled := True;

oMeter.SaveToCSV('usage.csv');

De demo: een factuurassistent

De nieuwe Delphi-demo in Demos\15.AI\02.Applications\11.AIAgent is een assistent voor een lijst met onbetaalde facturen. De agent heeft drie tools. list_unpaid_invoices en get_invoice zijn alleen-lezen, terwijl send_reminder gegevens wijzigt en eerst om je goedkeuring vraagt. De agent draait in een achtergrondthread, zodat het venster responsief blijft terwijl hij werkt. Met de demo kun je ook de rest van dit artikel uitproberen: geweigerde tools en PII-maskering, een budget per sessie met een live token- en kostenteller en een CSV-export, modelaliassen, een fallback-provider en een MCP-server waarvan de tools zijn vastgepind.

Documentatie die je codeeragent kan lezen

Een codeeragent schrijft alleen goede code voor de bibliotheken die hij kent. Elk component van sgcAI, sgcHTML, sgcCrypto en de andere packs van sgcWebSockets heeft nu een eigen Markdown-pagina voor taalmodellen, 224 pagina's in totaal. Elke pagina vertelt wat het component doet en verwijst naar de productpagina, en de meeste noemen de belangrijkste properties, methoden en events. De pagina van TsgcAIAgent, ai-agent.md, bevat ook een volledig gebruiksvoorbeeld. llms.txt wijst Claude Code, Copilot, Cursor en andere agents naar deze pagina's, zodat de agent de echte API leest voordat hij een regel schrijft.

De agentskills van sgcWebSockets leren de nieuwe componenten ook kennen. De AI-skill van 2026.11.0 heeft kant-en-klare patronen voor de agentloop, toolgoedkeuring, guardrails, modelaliassen met fallback en de usage meter, en elk patroon komt uit code die compileert. AI-agentskills voor Delphi, C++Builder en .NET legt uit hoe je ze installeert.

Beschikbaarheid

TsgcAIAgent, TsgcAIGuardrails, TsgcAIUsageMeter, de modelaliassen en fallbacks van TsgcAIChat en MCP tool pinning komen in sgcWebSockets 2026.11.0, voor Delphi 7 tot 13 en C++Builder. Ze maken deel uit van de sgcAI-componenten, inbegrepen in de edities Enterprise en All-Access en in het standalone product sgcAI. De help documenteert elke eigenschap, methode en gebeurtenis, met pagina's over de agentloop, over tools, over aliassen en fallbacks, over PII-maskering, over schemavalidatie en over tool pinning.

Lees meer op de componentpagina's van TsgcAIAgent, TsgcAIGuardrails en TsgcAIUsageMeter.

Lees verder

Vragen of feedback? Neem contact op. Je krijgt een antwoord van de mensen die de code hebben geschreven.