言語モデルに質問するだけなら簡単です。エージェントはその先へ進みます。モデルがあなたのどの関数を呼び出すかを決め、アプリケーションがそれを実行し、結果をモデルに返し、モデルが答えを出すまでループが続きます。このループを書くのは始まりにすぎません。出荷するアプリケーションには、モデルに触れさせてはならないものから遠ざけ、個人データをマシンの外に出さず、プロバイダーがモデルを廃止した日にも動き続け、ユーザーごとのコストを把握することも求められます。
sgcWebSockets 2026.11は、そのためのすべての部品を、Delphi 7から13向けのsgcAIコンポーネントに追加します。
TsgcAIAgentは、TsgcAIChatの7つのプロバイダー(OpenAI、Anthropic、Gemini、DeepSeek、Ollama、Grok、Mistral)すべてでツールループを実行します。ツールはあなたのDelphiメソッドとMCPサーバーのツールで、ツールがデータを変更する前には確認を求めます。TsgcAIGuardrailsは、実行してよいツールを決め、リクエストの前に個人データをマスキングして回答で元に戻し、構造化された回答をJSONスキーマで検証します。- MCPのツールピンニングは、信頼した後に定義が変わったサーバーのツールをブロックします。
- モデルエイリアスとプロバイダーのフォールバックにより、モデルが廃止されたりプロバイダーが停止したりしても、出荷済みのexeが応答を続けられます。
TsgcAIUsageMeterは、トークンを数えて料金を計算し、予算を適用して、すべてのリクエストをOpenTelemetryに送ります。
1つのコンポーネントで完結するエージェントループ
設定済みのTsgcAIChatをエージェントに渡し、ツールを記述してRunを呼び出します。エージェントはツール定義を添えてプロンプトを送信し、モデルが要求するツールをすべて実行して結果を返送し、モデルがプレーンテキストで答えるまで繰り返します。Runはその回答を返します。
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?'));
ツールは、モデルが書いたJSONオブジェクトとして引数を受け取り、結果をテキスト(通常は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;
エージェントは、すべてのプロバイダーでツール呼び出しに対応したTsgcAIChat.ChatWithToolsの上で動作します。ホスト型モデルからローカルのOllamaモデルへの切り替えはProviderとModelの変更だけで、ツールはそのままです。ループを自分で実行したい場合は、ChatWithToolsとAddToolResultも公開されています。
ループを制御する
Options.MaxStepsは1回の実行でのモデルリクエスト数を制限し、Cancelはボタン、タイマー、別のスレッドから実行を停止し、StopReasonは実行が終了した理由を示します。回答がトークン上限で打ち切られると、実行は aasrMaxTokens で停止し、その回答のツール呼び出しは、引数が不完全な可能性があるため実行されません。OnStepは各ステップを発生と同時に通知するため、ユーザーはエージェントが何をしているかを確認できます。
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;
失敗したツールは実行を終了させません。その例外はエラーテキストとしてモデルに返され、モデルは別の方法を試せます。チャット自体のエラー、たとえばネットワーク障害は、aasrErrorで実行を終了し、例外はあなたのコードに届きます。OnUsageは、実行中のすべてのモデルリクエストのトークン数を通知します。
Delphiメソッドをそのままツールにする
ツールは、上のように名前、説明、型付きパラメータ、ハンドラーで構成されます。すでに目的の処理をするメソッドがある場合は、AddMethodでそのまま公開できます。ツール名はメソッド名になり、パラメータ、その型、結果は拡張RTTIで読み取られるため、この方法にはDelphi 2010以降が必要です。
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');
入力がより複雑なツールには、InputSchemaに独自のJSONスキーマを設定します。Enabledを使うと、ツールを削除せずに以降の実行から外せます。
MCPサーバーのツール
1つ以上のMCPクライアントをMCPClientsに追加すると、エージェントはそれらのサーバーのツールを自分のツールと並べてモデルに提示します。ツールは実行ごとに1回だけ一覧取得され、モデルが要求したときにクライアント経由で呼び出されます。サーバーがreadOnlyHintを付けたツールは読み取り専用として扱われ、ローカルツールとサーバーのツールが同名の場合はローカルが優先されます。
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'));
変更が起きる前の承認
すべてのツールにはReadOnlyフラグがあります。読み取り専用のツールは、モデルが要求した時点で実行されます。データを変更する、メールを送る、お金を使うといったツールは読み取り専用にせず、エージェントは実行前に、ツール名とモデルが選んだ正確な引数を添えてOnToolApprovalを呼び出します。
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;
拒否されたツールはエラーではありません。モデルにはそのツールが承認されなかったことが伝えられ、モデルはそのツールなしでユーザーに回答できます。次のセクションのようにガードレールを割り当てている場合、承認が必要なのにハンドラーがないツールは拒否されるため、イベントの設定忘れで書き込みツールが通ってしまうことはありません。
ガードレール
TsgcAIGuardrailsはチャットに割り当てる独立したコンポーネントなので、同じルールがエージェントにも通常のチャットリクエストにも適用されます。役割は3つあります。
- ツールルール。
AllowedToolsとDeniedToolsは*ワイルドカード付きの名前を受け取り、拒否は常に優先されます。許可されていないツールはモデルに提示されません。ToolApprovalは、承認が必要なツールを、なし、読み取り専用でないもの、すべてのいずれかから選びます。 - 個人データ。
PII.Enabledを有効にすると、メールアドレス、電話番号、カード番号、IBAN、IPアドレス、独自のパターンが、リクエストがアプリケーションを出る前にプレースホルダーに置き換えられ、回答では元の値に戻されます。 - 回答スキーマ。
Schema.Enabledを有効にすると、JSONスキーマに一致しない構造化された回答は、たとえば$.price: expected numberのようなエラーのパスとともに拒否され、OnSchemaViolationで通知されます。
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;
マスキングを自分で呼び出して、プロバイダーが受け取る内容と、アプリケーションが受け取る内容を確認できます。
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
MCPサーバーのツールを固定する
MCPツールの説明と入力スキーマは、モデルが指示として読み取ります。信頼した後でサーバーがそれらを変更すると、侵害されたためでも所有者が変更したためでも、すでに使っているツールに新しい指示が紛れ込む可能性があります。これはツールポイズニングと呼ばれます。
ツールピンニングを使うと、TsgcWSAPIClient_MCPは、各ツールを初めて一覧取得したときに、その名前、説明、入力スキーマ、アノテーションのSHA-256フィンガープリントを保持します。後の一覧で異なる定義が返されると、そのツールはブロックされます。一覧から除外され、エージェントは提示せず、そのツールの呼び出しは何かが送信される前に例外を発生させます。OnMCPToolChangedで変更を受け入れるか、レビューのためにブロックしたままにでき、サーバーの他のツールは引き続き動作します。
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;
廃止されたモデルでも出荷済みのexeは止まらない
モデル名は、インストール済みのアプリケーションよりも速く変わります。exeにコンパイルされたモデル名は、プロバイダーがそれを廃止した日に動かなくなり、すべての顧客に新しいビルドが必要になります。モデルエイリアスを使うと、コードはsmartやfastを要求し、エイリアスがモデルと、必要ならプロバイダーを決めます。一覧はファイル、JSON、URLから読み込めるため、新しいモデルへの移行は新しいリリースではなく、サーバー側の変更で済みます。
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');
フォールバックは障害に備えます。プロバイダーが接続エラー、HTTP 5xx、HTTP 429、または未知のモデルで失敗すると、同じリクエストが一覧の次のプロバイダーとモデルに送られます。会話履歴、ツール、JSON回答は引き継がれ、OnFallbackが切り替えを通知します。ローカルのOllamaモデルは、最後の手段として適しています。
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;
すべてのリクエストのコストを把握する
TsgcAIUsageMeterは、キャッシュされた入力トークンを含むすべてのリクエストのトークン数を記録し、与えられた価格表で料金を計算して、ユーザーごとおよびセッションごとの合計を保持します。チャットに割り当て、UserIdとSessionIdを設定します。メーターはチャットに付くため、エージェントの実行中のすべてのモデルリクエストも集計されます。
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')]));
価格表はワイルドカードでプロバイダーとモデルに一致し、JSONまたはファイルから読み込めるため、価格の変更にも新しいビルドは不要です。予算を設定すると、メーターは上限になります。予算はアプリケーション全体、ユーザーごと、セッションごとに、1日、1か月、または累計で、金額またはトークン数で設定できます。予算を使い切ると、次のリクエストは送信前に拒否されるため、予算はエージェントの実行を途中で止めることもできます。
// 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;
予算を警告だけにしたい場合は、BlockWhenExceededをFalseにするとOnBudgetExceededだけが発生します。レポート用に、レコードはCSVファイルや任意のTDataSetに出力できます。OpenTelemetryを有効にすると、すべてのリクエストがGenAIセマンティック規約に沿ったスパンになり、バックグラウンドでOTLP/HTTPコレクターに送信されるため、チャットが遅くなることはありません。
oMeter.OpenTelemetry.Endpoint := 'http://localhost:4318';
oMeter.OpenTelemetry.ServiceName := 'invoice-assistant';
oMeter.OpenTelemetry.Enabled := True;
oMeter.SaveToCSV('usage.csv');
デモ:請求書アシスタント
Demos\15.AI\02.Applications\11.AIAgentの新しいDelphiデモは、未払い請求書の一覧を扱うアシスタントです。エージェントには3つのツールがあります。list_unpaid_invoicesとget_invoiceは読み取り専用で、send_reminderはデータを変更するため、先に承認を求めます。エージェントはバックグラウンドスレッドで動作するので、処理中もウィンドウは応答し続けます。デモでは、この記事の残りの機能も試せます。拒否ツールとPIIマスキング、ライブのトークンおよびコストカウンター付きのセッション単位の予算とCSVエクスポート、モデルエイリアス、フォールバックプロバイダー、ツールが固定されたMCPサーバーです。
コーディングエージェントが読めるドキュメント
コーディングエージェントが良いコードを書けるのは、知っているライブラリだけです。sgcAI、sgcHTML、sgcCryptoをはじめとするsgcWebSocketsの各パックのすべてのコンポーネントに、言語モデル向けの専用Markdownページが用意され、合計224ページになりました。各ページにはコンポーネントの役割と製品ページへのリンクがあり、多くのページで主要なプロパティ、メソッド、イベントも一覧できます。TsgcAIAgentのページai-agent.mdには、完全な使用例も含まれています。llms.txtがClaude Code、Copilot、Cursorなどのエージェントをこれらのページに案内するので、エージェントは1行も書く前に実際のAPIを読み込みます。
sgcWebSocketsのエージェントスキルも新しいコンポーネントに対応しました。2026.11.0のAIスキルには、エージェントループ、ツールの承認、ガードレール、フォールバック付きのモデルエイリアス、使用量メーターのすぐ使えるパターンが含まれ、どのパターンもコンパイルできるコードから作られています。インストール方法はDelphi、C++Builder、.NET向けAIエージェントスキルで説明しています。
提供時期
TsgcAIAgent、TsgcAIGuardrails、TsgcAIUsageMeter、TsgcAIChatのモデルエイリアスとフォールバック、MCPのツールピンニングは、Delphi 7から13とC++Builder向けのsgcWebSockets 2026.11.0で登場します。これらはsgcAIコンポーネントの一部で、EnterpriseとAll-Accessエディション、および単体製品のsgcAIに含まれています。ヘルプには、エージェントループ、ツール、エイリアスとフォールバック、PIIマスキング、スキーマ検証、ツールピンニングのページがあり、すべてのプロパティ、メソッド、イベントを説明しています。
TsgcAIAgent、TsgcAIGuardrails、TsgcAIUsageMeterのコンポーネントページで詳しく読めます。
次に読む
- DelphiでAIエージェントを構築する: 関数呼び出しとMCPツール利用、エージェントループをステップごとに解説
- Delphi MCP クライアント
- Delphi における OpenAI 対 Anthropic 対 Gemini:どの AI API を使うべきか?
ご質問やご意見はありますか。お問い合わせください。コードを書いた本人から返信いたします。
