HelpStackDocs

Integraciones

Herramientas de agente personalizadas

Dé a la IA acceso en tiempo real a sus sistemas: defina herramientas que el LLM puede llamar durante la generación de respuestas para obtener el estado de pedidos, consultar cuentas, verificar inventario y más.

Datos rápidos#

Tipos de herramientaSERVER_SIDE (llamada HTTP desde HelpStack) o CLIENT_SIDE (se ejecuta en el navegador del visitante)
Gestionadas mediantePanel de control + /api/agent-tools (nivel de organización), /api/channels/[id]/agent-tools (nivel de canal)
AutenticaciónSesión del panel (las configura su equipo)
Tiempo de espera de herramienta de servidor~10s, respuesta limitada a 128 KB
Tiempo de espera de herramienta de cliente~5s (round-trip por socket)
RegistroCada llamada se registra en ToolCallLog

Para la visión conceptual, consulte la guía de herramientas de agente. Esta página es la referencia técnica.

Anatomía de una herramienta#

Una definición de herramienta tiene estos campos:

CampoRequeridoNotas
NameEl nombre de función que llama el LLM, p. ej. get_order_status. Debe ser un identificador de función válido
DescriptionEl LLM lo lee para decidir cuándo llamar a la herramienta. Máx ~2000 caracteres (validado). Sea específico
URLsí (lado servidor)Debe ser HTTPS. Protegida contra SSRF: una lista de bloqueo rechaza objetivos de red interna/privada
MethodnoGET | POST | PUT | PATCH. Por defecto POST
HeadersnoObjeto JSON opcional. Puede cifrarse/enmascararse (úselo para claves de API/tokens)
Parameters SchemaUn objeto JSON Schema que describe los argumentos que el LLM rellena (parametersSchema)
TypeSERVER_SIDE o CLIENT_SIDE
ActiveInterruptor para activar/desactivar la herramienta sin eliminarla

Ejemplo práctico — get_order_status (SERVER_SIDE)#

Definición de la herramienta

CampoValor
Nameget_order_status
TypeSERVER_SIDE
MethodPOST
URLhttps://api.YOURCOMPANY.com/orders/status
Headers{ "Authorization": "Bearer YOUR_API_TOKEN" } (almacenar como cabecera cifrada)
DescriptionLook up the current status and tracking info for a customer order by its order number. Call this whenever the customer asks where their order is, when it will arrive, or to confirm an order was placed.

Esquema de parámetros (JSON Schema)

{
  "type": "object",
  "properties": {
    "order_number": {
      "type": "string",
      "description": "The customer's order number, e.g. ORD-10432"
    }
  },
  "required": ["order_number"]
}

Solicitud que recibe su endpoint

En una llamada a la herramienta, HelpStack envía los argumentos proporcionados por el LLM como cuerpo de la solicitud (para POST/PUT/PATCH), con sus cabeceras configuradas más Content-Type: application/json:

POST /orders/status HTTP/1.1
Host: api.YOURCOMPANY.com
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json

{ "order_number": "ORD-10432" }

Contrato de respuesta que debe respetar su endpoint

Devuelva JSON que el modelo pueda leer. La llamada expira en ~10s y la respuesta está limitada a 128 KB.

Una respuesta por encima del límite se rechaza, no se trunca — medio documento JSON o no se analiza o, peor, se analiza con otro significado —, así que la llamada falla y se le indica a la IA que la herramienta no está disponible. Lo mismo ocurre si el cuerpo no es JSON, normalmente una página de error HTML de un proxy.

El límite es una protección frente a un endpoint desbocado (una lista paginada sin tamaño de página, un volcado de depuración), no un objetivo al que apuntar. El tamaño le cuesta también por debajo: lo que devuelva se analiza por completo, se escribe en el registro de llamadas y se pone delante del modelo, así que se paga en tokens en cada respuesta que lo use. Devuelva los pocos campos que la IA necesita para responder, no su registro completo: el estado de un pedido, su fecha estimada y el número de seguimiento, no el objeto del pedido entero.

{
  "status": "shipped",
  "carrier": "DHL",
  "tracking_number": "JD0140...",
  "estimated_delivery": "2026-06-02"
}

La IA recibe este payload como resultado de la herramienta y lo incorpora en su respuesta. No hay un envoltorio requerido — devuelva los campos que sean útiles, pero hágalos autodescriptivos para que el modelo los use correctamente.

Cómo escribir buenas descripciones#

La descripción es el campo más importante — es lo único que usa el LLM para decidir si y cuándo llamar a la herramienta.

  • Indique qué devuelve la herramienta y cuándo llamarla ("Llama a esto cuando el cliente pregunte ...").
  • Mencione las frases que los clientes realmente usan.
  • Describa cada parámetro claramente en el description de su esquema.
  • Mantenga el límite de ~2000 caracteres (el diálogo de guardado valida y muestra errores).

Seguridad y protección contra SSRF#

  • Solo HTTPS. Las URLs HTTP sin cifrar son rechazadas.
  • Lista de bloqueo SSRF. Los objetivos de red interna/privada (loopback, rangos RFC1918 privados, enlace local, endpoints de metadatos) están bloqueados para que una herramienta no pueda apuntar a infraestructura interna.
  • Cabeceras secretas. Ponga claves de API/tokens en Headers, que pueden cifrarse/enmascararse en lugar de almacenarse en texto plano.
  • Trate el endpoint de la herramienta como accesible desde Internet — autentique las solicitudes (p. ej. un token bearer en Headers) y valide la entrada en su lado.

Tiempos de espera y límites#

Lado servidorLado cliente
Tiempo de espera~10s~5s
Límite de respuesta128 KB (rechazada, no truncada)
En caso de fallo/tiempo de esperaRegistrado; la IA es informada de que la herramienta no está disponible y continúa con la mejor respuesta que pueda dar

Los fallos degradan con elegancia — una herramienta rota o lenta nunca bloquea una respuesta; la IA simplemente continúa sin esos datos.

Herramientas de organización vs. de canal + filtrado semántico#

  • Las herramientas pueden definirse a nivel de organización y a nivel de canal.
  • Para una conversación dada, las herramientas de nivel de organización y de canal se fusionan, y una herramienta de canal reemplaza a una herramienta de organización con el mismo nombre.
  • El conjunto fusionado se filtra semánticamente por relevancia para la consulta del cliente, de modo que un catálogo grande de herramientas no sobrecarga cada llamada al LLM — solo se ofrecen las herramientas relevantes.
  • Las herramientas seleccionadas se convierten en definiciones de función de OpenAI/Anthropic y se ofrecen durante la generación de respuestas.

Importación desde OpenAPI#

Puede crear herramientas a partir de una API existente: pegue o suba una especificación OpenAPI 3.0 y HelpStack extrae sus operaciones en definiciones de herramienta que usted revisa y guarda. Esta es la forma más rápida de exponer una API REST existente a la IA.

Registro#

Cada llamada a una herramienta se registra en ToolCallLog. Vea el historial de llamadas de una herramienta (argumentos, resultado, tiempo) mediante:

GET /api/agent-tools/[id]/logs

Use los registros para depurar por qué una herramienta fue o no fue llamada, y para detectar tiempos de espera/errores.

Herramientas del lado del cliente (avanzado)#

Las herramientas CLIENT_SIDE se declaran de la misma manera (nombre, descripción, esquema de parámetros) pero se ejecutan en el navegador del visitante del sitio web, mediadas por el socket del widget:

  1. La IA emite una llamada a herramienta para una herramienta CLIENT_SIDE.
  2. El servidor emite tool:execute al widget.
  3. El widget la ejecuta y responde con tool:result (o tool:error).
  4. Hay un tiempo de espera de ~5s; en caso de tiempo de espera la llamada degrada con elegancia (herramienta no disponible).

Registrar un handler

El script del widget expone window.HelpStack. Registre un handler por nombre y se llamará cada vez que la IA invoque esa herramienta:

<script src="https://helpstack.eu/widget.js?id=CHANNEL_ID" async></script>
<script>
  window.HelpStack = window.HelpStack || function () {
    (window.HelpStack.q = window.HelpStack.q || []).push(arguments);
  };

  window.HelpStack('registerTool', 'highlight', async ({ section }) => {
    document.querySelector(`#${section}`)?.scrollIntoView({ behavior: 'smooth' });
    return { highlighted: section };
  });
</script>

El shim de cola del medio significa que puede registrar antes de que widget.js se haya cargado; el script vacía la cola al arrancar. Lo que devuelva su handler es lo que lee la IA, y si lanza una excepción, a la IA se le dice que la herramienta falló. La misma función está también en ChatWidget.registerTool(name, fn) una vez cargado el script.

Handlers de ejemplo

Cuatro patrones, en el orden en que los equipos suelen construirlos. Cada uno muestra la definición de la herramienta que introduce en el panel y el handler que registra en su página. Son deliberadamente pequeños: una herramienta del lado del cliente debe hacer una sola cosa y devolver un objeto corto y simple.

1. Leer dónde está el visitante (sin parámetros)

La primera herramienta más útil. No cuesta nada y evita que la IA adivine qué está mirando el visitante.

Definición

CampoValor
Nombrewhere_is_the_user
TipoCLIENT_SIDE
DescripciónCheck which page and section the customer is currently looking at. Call this before giving directions, so you can describe what is actually on their screen.
{ "type": "object", "properties": {}, "required": [] }

Handler

window.HelpStack('registerTool', 'where_is_the_user', async () => ({
  page: location.pathname,
  title: document.title,
  // Whatever "where they are" means in your UI: an open tab, a wizard step, a route.
  section: document.querySelector('[data-active-section]')?.dataset.activeSection ?? null,
}));

2. Señalar algo en la página

Definición

CampoValor
Nombrehighlight
TipoCLIENT_SIDE
DescripciónScroll to and visually highlight a control or section on the customer's screen so they can see exactly what you mean. Use this instead of describing where something is.
{
  "type": "object",
  "properties": {
    "target": {
      "type": "string",
      "description": "What to highlight, in the customer's own words: a button label, a section name, or a menu item."
    }
  },
  "required": ["target"]
}

Handler

window.HelpStack('registerTool', 'highlight', async ({ target }) => {
  const el = document.querySelector(`[data-tour="${CSS.escape(target)}"]`);
  if (!el) {
    // Not found is a normal outcome, not a failure. Say so and let the AI recover.
    return { found: false, target };
  }
  el.scrollIntoView({ behavior: 'smooth', block: 'center' });
  el.classList.add('hs-highlight');
  setTimeout(() => el.classList.remove('hs-highlight'), 3000);
  return { found: true, target };
});

3. Leer contenido de vuelta a la IA

Definición

CampoValor
Nombreread_section
TipoCLIENT_SIDE
DescripciónRead what is currently written in one section of the page, field by field, so you can answer about the customer's real content instead of guessing.
{
  "type": "object",
  "properties": {
    "section": { "type": "string", "description": "Section name as the customer would say it." }
  },
  "required": ["section"]
}

Handler

window.HelpStack('registerTool', 'read_section', async ({ section }) => {
  const root = document.querySelector(`[data-section="${CSS.escape(section)}"]`);
  if (!root) return { found: false, section };

  const fields = {};
  root.querySelectorAll('[data-field]').forEach((el) => {
    // Keep it small — the result goes into the AI's context on every call.
    fields[el.dataset.field] = (el.value ?? el.textContent ?? '').trim().slice(0, 500);
  });
  return { found: true, section, fields };
});

Devuelva claves de campo que la IA pueda citarle de vuelta. Eso es lo que hace posible una herramienta de escritura como la siguiente.

4. Escribir algo para el visitante

El patrón de mayor valor y con el que hay que tener más cuidado: la IA está cambiando ahora la pantalla del visitante.

Definición

CampoValor
Nombrefill_field
TipoCLIENT_SIDE
DescripciónPut suggested text into a specific field on the customer's page so they can review it and save it themselves. Only use a field key that read_section returned. Never save or submit on the customer's behalf.
{
  "type": "object",
  "properties": {
    "section": { "type": "string", "description": "Section name, as returned by read_section." },
    "field":   { "type": "string", "description": "Field key exactly as read_section returned it." },
    "text":    { "type": "string", "description": "The complete new contents of that field." }
  },
  "required": ["section", "field", "text"]
}

Handler

window.HelpStack('registerTool', 'fill_field', async ({ section, field, text }) => {
  const el = document.querySelector(
    `[data-section="${CSS.escape(section)}"] [data-field="${CSS.escape(field)}"]`
  );
  if (!el) return { written: false, reason: 'field not found', section, field };

  el.value = text;
  // Frameworks that track their own state need to be told.
  el.dispatchEvent(new Event('input', { bubbles: true }));
  el.scrollIntoView({ behavior: 'smooth', block: 'center' });
  return { written: true, section, field };
});

⚠️ Rellénelo, no lo envíe. Deje que el visitante lea el cambio y pulse guardar él mismo. Una herramienta que publica en su nombre convierte una sugerencia útil en una acción irrecuperable, y la IA no puede ver el resultado.

Escribir handlers que se comporten

  • Devuelva objetos pequeños y simples. El resultado se inyecta en el contexto de la IA en cada llamada. Devuelva { found: true, section, fields }, nunca un nodo del DOM, un componente o un blob de 50 KB.
  • «No encontrado» es un resultado, no un error. Devuelva { found: false } y deje que la IA diga algo sensato. Lanzar una excepción produce un fallo de herramienta, y la IA solo aprende que algo se rompió.
  • Manténgase muy por debajo del timeout de ~5 s. Nada de llamadas de red que el visitante tenga que esperar. Si necesita su backend, conviértalo en una herramienta SERVER_SIDE.
  • Nunca exponga nada que el visitante no pueda ver ya. Una herramienta del lado del cliente se ejecuta en una página pública sin autenticación propia. No lea tokens de autenticación, datos de otros clientes ni nada detrás de sus propias comprobaciones de permisos.
  • Nombre los parámetros en el idioma del cliente, no en el de su esquema. La IA los rellena con lo que escribió el cliente. target: "el botón de guardar" funciona; elementId: "btn-save-primary" no.

El protocolo subyacente

registerTool es una capa fina sobre postMessage, y puede responder los mensajes usted mismo — útil si ya tiene un enrutador de mensajes, o si quiere servir una herramienta que el widget no conoce.

⚠️ Si registra un handler mediante window.HelpStack, no responda además esa misma herramienta con su propio listener. El iframe resuelve la primera respuesta por callId, así que dos respuestas compiten. El widget guarda silencio ante herramientas para las que no tiene handler, precisamente para que un listener propio existente siga funcionando.

El iframe del widget envía cada llamada del lado del cliente a su ventana padre:

{ type: 'TOOL_EXECUTE', callId: '…', toolName: 'highlight', parameters: { … } }

Después espera una de estas de vuelta, emparejada por callId:

{ type: 'TOOL_RESULT', callId: '…', result: { … } }   // whatever the AI should read
{ type: 'TOOL_ERROR',  callId: '…', error: 'why it failed' }

Así que basta con un handler en su propia página:

window.addEventListener('message', (event) => {
  // Required. Without this check any page in an iframe could drive your tools.
  if (event.origin !== 'https://helpstack.eu') return;
  const { type, callId, toolName, parameters } = event.data ?? {};
  if (type !== 'TOOL_EXECUTE') return;

  const reply = (result) =>
    event.source.postMessage({ type: 'TOOL_RESULT', callId, result }, event.origin);
  const fail = (error) =>
    event.source.postMessage({ type: 'TOOL_ERROR', callId, error }, event.origin);

  if (toolName === 'highlight') {
    const el = document.querySelector(parameters.selector);
    if (!el) return fail('No element matches that selector.');
    el.scrollIntoView({ behavior: 'smooth', block: 'center' });
    el.classList.add('my-highlight');
    reply({ ok: true, message: 'Highlighted it. Tell the customer to look at the ring.' });
  }
});

Tres cosas que conviene saber antes de construir sobre esto:

  • Responda dentro del timeout de 5 s, y responda con honestidad. Al agotarse el tiempo, la llamada se resuelve como { success: false, error: 'timeout' } y la IA continúa sin ella. No responda antes de saber que la acción funcionó: una IA a la que se le dice «resaltado» cuando no se resaltó nada mandará al cliente a buscar algo que no está en su pantalla, y eso es peor que no tener herramienta.
  • Lo que ponga en result es lo que lee la IA. Una frase explicando qué ocurrió le da una respuesta mejor que { ok: true }.
  • Puede que el visitante no esté en una página capaz de hacerlo. Devuelva un resultado que lo diga, en lugar de fallar en silencio, para que la IA pueda explicarlo.

Combine esto con window.ChatWidget.identify(identity, metadata) para que la IA sepa a qué cuenta está ayudando antes de llamar a nada.

Relacionado#