Prepara tu app Delphi para agentes

· Componentes
App Delphi lista para agentes: TsgcAIAgent

Hacerle una pregunta a un modelo de lenguaje es la parte fácil. Un agente va más lejos. El modelo decide a cuál de tus funciones llamar, tu aplicación las ejecuta, los resultados vuelven al modelo y el bucle continúa hasta que el modelo tiene una respuesta. Escribir ese bucle es solo el principio. Una aplicación en producción también tiene que mantener al modelo lejos de lo que no debe tocar, conservar los datos personales en la máquina, sobrevivir el día en que un proveedor retira un modelo y saber cuánto cuesta cada usuario.

sgcWebSockets 2026.11 añade a los componentes sgcAI las piezas para todo eso, para Delphi 7 a 13:

El bucle del agente en un componente

Dale al agente un TsgcAIChat configurado, describe una herramienta y llama a Run. El agente envía el prompt con las definiciones de herramientas, ejecuta cada herramienta que pide el modelo, devuelve los resultados y repite hasta que el modelo responde en texto plano. Run devuelve esa respuesta.

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

La herramienta recibe sus argumentos como el objeto JSON que escribió el modelo, y devuelve su resultado como texto, normalmente 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;

El agente trabaja sobre TsgcAIChat.ChatWithTools, que ahora admite tool calling en todos los proveedores. Pasar de un modelo alojado a un modelo local de Ollama es un cambio de Provider y Model, y las herramientas siguen siendo las mismas. Si prefieres ejecutar el bucle tú mismo, ChatWithTools y AddToolResult también son públicos.

Controla el bucle

Options.MaxSteps limita el número de peticiones al modelo en una ejecución, Cancel detiene una ejecución desde un botón, un temporizador u otro hilo, y StopReason te dice por qué terminó. Cuando una respuesta se corta por el límite de tokens, la ejecución termina con aasrMaxTokens y las llamadas a herramientas de esa respuesta no se ejecutan, porque sus argumentos pueden estar incompletos. OnStep informa de cada paso según ocurre, de modo que el usuario puede ver lo que hace el 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;

Una herramienta que falla no termina la ejecución. Su excepción vuelve al modelo como texto de error, y el modelo puede probar otra cosa. Un error del propio chat, por ejemplo un fallo de red, termina la ejecución con aasrError y la excepción llega a tu código. OnUsage informa de los tokens de cada petición al modelo de la ejecución.

Tus métodos Delphi como herramientas

Una herramienta es un nombre, una descripción, parámetros tipados y un manejador, como arriba. Cuando ya tienes un método que hace el trabajo, AddMethod lo publica tal cual. El nombre de la herramienta es el nombre del método, y los parámetros, sus tipos y el resultado se leen con RTTI extendido, así que esta opción necesita Delphi 2010 o posterior.

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

Para una herramienta con una entrada más rica, asigna a InputSchema tu propio esquema JSON. Enabled saca una herramienta de las siguientes ejecuciones sin eliminarla.

Herramientas de tus servidores MCP

Añade uno o varios clientes MCP a MCPClients y el agente ofrece las herramientas de esos servidores junto a las tuyas. Se listan una vez por ejecución y se llaman a través del cliente cuando el modelo las pide. Una herramienta que el servidor marca con readOnlyHint cuenta como de solo lectura, y cuando una herramienta local y una de servidor comparten nombre, gana la local.

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

Aprobación antes de que cambie algo

Cada herramienta tiene un indicador ReadOnly. Las herramientas de solo lectura se ejecutan en cuanto el modelo las pide. Una herramienta que modifica datos, envía un correo o gasta dinero no es de solo lectura, y el agente pregunta a OnToolApproval antes de ejecutarla, con el nombre de la herramienta y los argumentos exactos que eligió el modelo.

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;

Una herramienta rechazada no es un error. Se le dice al modelo que la herramienta no fue aprobada y puede responder al usuario sin ella. Con guardrails asignados, como en la siguiente sección, una herramienta que necesita aprobación y no encuentra manejador se rechaza, de modo que un evento olvidado nunca deja pasar una herramienta de escritura.

Guardrails

TsgcAIGuardrails es un componente aparte que asignas al chat, de modo que las mismas reglas se aplican al agente y a las peticiones de chat normales. Tiene tres funciones.

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;

Puedes llamar tú mismo a la redacción para ver lo que recibirá el proveedor y lo que recupera tu aplicación:

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

Fija las herramientas de tus servidores MCP

El modelo lee la descripción y el esquema de entrada de una herramienta MCP como instrucciones. Un servidor que las cambia después de que confiaras en él, porque fue comprometido o porque su propietario lo modificó, puede ocultar nuevas instrucciones en una herramienta que ya usas. Esto se conoce como tool poisoning.

Con el pinning de herramientas, TsgcWSAPIClient_MCP guarda una huella SHA-256 del nombre, la descripción, el esquema de entrada y las anotaciones de cada herramienta la primera vez que la lista. Cuando una lista posterior devuelve una definición distinta, la herramienta se bloquea. Se elimina de la lista, el agente nunca la ofrece y una llamada a ella lanza una excepción antes de enviar nada. OnMCPToolChanged te permite aceptar el cambio o mantener la herramienta bloqueada para revisarla, y las demás herramientas del servidor siguen funcionando.

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 modelo retirado no detendrá un exe en producción

Los nombres de modelo cambian más rápido que las aplicaciones instaladas. Un nombre de modelo compilado en un exe deja de funcionar el día en que el proveedor lo retira, y cada cliente necesita una compilación nueva. Con los alias de modelo, el código pide smart o fast, y el alias decide el modelo y, si quieres, el proveedor. La lista se carga desde un archivo, desde JSON o desde una URL, de modo que pasar a un modelo nuevo es un cambio en tu servidor, no una nueva versión.

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

Los fallbacks cubren las caídas. Cuando el proveedor falla con un error de conexión, un HTTP 5xx, un HTTP 429 o un modelo que no conoce, la misma petición pasa al siguiente proveedor y modelo de la lista. El historial de la conversación, las herramientas y las respuestas JSON se conservan, y OnFallback te avisa de que ocurrió. Un modelo local de Ollama es un buen último recurso.

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;

Sabe lo que cuesta cada petición

TsgcAIUsageMeter registra los tokens de cada petición, incluidos los tokens de entrada en caché, calcula su precio con la lista de precios que le des, y mantiene totales por usuario y por sesión. Asígnalo al chat y establece UserId y SessionId. Como el medidor está en el chat, también se cuenta cada petición al modelo de una ejecución del 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')]));

La lista de precios hace coincidir proveedor y modelo con comodines y puede cargarse desde JSON o desde un archivo, así que un cambio de precio tampoco necesita una compilación nueva. Los presupuestos convierten el medidor en un límite. Un presupuesto cubre toda la aplicación, cada usuario o cada sesión, por día, por mes o en total, en dinero o en tokens. Cuando se agota un presupuesto, la siguiente petición se rechaza antes de enviarse, de modo que un presupuesto también puede detener una ejecución del agente a medias.

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

Asigna False a BlockWhenExceeded para un presupuesto que solo lanza OnBudgetExceeded como alerta. Para informes, los registros van a un archivo CSV o a cualquier TDataSet. Con OpenTelemetry activado, cada petición se convierte en un span que sigue las convenciones semánticas GenAI y va a tu colector OTLP/HTTP en segundo plano, sin ralentizar el chat.

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

oMeter.SaveToCSV('usage.csv');

La demo: un asistente de facturas

La nueva demo de Delphi en Demos\15.AI\02.Applications\11.AIAgent es un asistente para una lista de facturas impagadas. El agente tiene tres herramientas. list_unpaid_invoices y get_invoice son de solo lectura, mientras que send_reminder modifica datos y pide tu aprobación primero. El agente se ejecuta en un hilo en segundo plano, así que la ventana sigue respondiendo mientras trabaja. La demo también te permite probar el resto de este artículo: herramientas denegadas y redacción de PII, un presupuesto por sesión con un contador de tokens y coste en vivo y exportación a CSV, alias de modelo, un proveedor de fallback y un servidor MCP cuyas herramientas están fijadas.

Documentación que tu agente de código puede leer

Un agente de código solo escribe buen código para las bibliotecas que conoce. Cada componente de sgcAI, sgcHTML, sgcCrypto y de los demás packs de sgcWebSockets tiene ahora su propia página Markdown para modelos de lenguaje, 224 páginas en total. Cada página explica qué hace el componente y enlaza su página de producto, y la mayoría enumera sus propiedades, métodos y eventos principales. La página de TsgcAIAgent, ai-agent.md, incluye además un ejemplo de uso completo. llms.txt dirige a Claude Code, Copilot, Cursor y otros agentes hacia estas páginas, de modo que el agente lee la API real antes de escribir una línea.

Las skills de agente de sgcWebSockets también aprenden los nuevos componentes. La skill de IA de 2026.11.0 trae patrones listos para el bucle del agente, la aprobación de herramientas, los guardrails, los alias de modelo con fallback y el medidor de uso, y cada patrón sale de código que compila. Skills de agentes de IA para Delphi, C++Builder y .NET explica cómo instalarlas.

Disponibilidad

TsgcAIAgent, TsgcAIGuardrails, TsgcAIUsageMeter, los alias de modelo y los fallbacks de TsgcAIChat y el pinning de herramientas MCP llegan en sgcWebSockets 2026.11.0, para Delphi 7 a 13 y C++Builder. Forman parte de los componentes sgcAI, incluidos en las ediciones Enterprise y All-Access y en el producto independiente sgcAI. La ayuda documenta cada propiedad, método y evento, con páginas sobre el bucle del agente, las herramientas, los alias y fallbacks, la redacción de PII, la validación de esquemas y el pinning de herramientas.

Lee más en las páginas de componente de TsgcAIAgent, TsgcAIGuardrails y TsgcAIUsageMeter.

Sigue leyendo

¿Preguntas o comentarios? Ponte en contacto. Recibirás respuesta de las personas que escribieron el código.