언어 모델에 질문하는 것은 쉬운 부분입니다. 에이전트는 한 걸음 더 나아갑니다. 모델이 여러분의 함수 중 무엇을 호출할지 정하고, 애플리케이션이 그것을 실행하고, 결과가 모델로 돌아가며, 모델이 답을 낼 때까지 루프가 계속됩니다. 이 루프를 작성하는 것은 시작에 불과합니다. 배포된 애플리케이션은 모델이 건드려서는 안 되는 것에서 모델을 떼어 놓고, 개인 데이터를 컴퓨터 안에 두고, 공급자가 모델을 폐기하는 날을 견디고, 사용자마다 비용이 얼마인지 알아야 합니다.
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로 보냅니다.
하나의 컴포넌트로 만드는 에이전트 루프
에이전트에 구성된 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도 public입니다.
루프 제어하기
Options.MaxSteps는 한 번의 실행에서 보내는 모델 요청 수를 제한하고, 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 서버의 도구
하나 이상의 MCP 클라이언트를 MCPClients에 추가하면 에이전트가 해당 서버의 도구를 여러분의 도구와 함께 제공합니다. 도구는 실행마다 한 번 조회되고, 모델이 요청하면 클라이언트를 통해 호출됩니다. 서버가 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는 채팅에 지정하는 별도의 컴포넌트이므로, 같은 규칙이 에이전트와 일반 채팅 요청 모두에 적용됩니다. 세 가지 역할이 있습니다.
- 도구 규칙.
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 도구의 설명과 입력 스키마는 모델이 지시문으로 읽습니다. 서버가 침해되었거나 소유자가 바꾸었다는 이유로 신뢰한 뒤에 이를 변경하면, 이미 사용 중인 도구에 새로운 지시를 숨길 수 있습니다. 이를 도구 포이즈닝(tool poisoning)이라고 합니다.
도구 핀닝을 쓰면 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이나 파일에서 불러올 수 있으므로, 가격이 바뀌어도 새 빌드는 필요 없습니다. 예산은 미터를 한도로 바꿔 줍니다. 예산은 애플리케이션 전체, 사용자별, 세션별로 하루, 한 달, 또는 전체 기간에 대해 금액이나 토큰으로 설정할 수 있습니다. 예산을 모두 사용하면 다음 요청은 전송되기 전에 거부되므로, 예산이 에이전트 실행을 중간에 멈추게 할 수도 있습니다.
// 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 시맨틱 규약을 따르는 span이 되어 백그라운드에서 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 데모는 미결제 인보이스 목록을 다루는 어시스턴트입니다. 에이전트에는 도구가 세 개 있습니다. 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 등의 에이전트를 이 페이지들로 안내하므로, 에이전트는 코드를 한 줄 쓰기 전에 실제 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 vs Anthropic vs Gemini: 어떤 AI API를 써야 할까요?
질문이나 의견이 있으신가요? 문의하기. 코드를 직접 작성한 사람들이 답변해 드립니다.
