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 herramienta | SERVER_SIDE (llamada HTTP desde HelpStack) o CLIENT_SIDE (se ejecuta en el navegador del visitante) |
| Gestionadas mediante | Panel de control + /api/agent-tools (nivel de organización), /api/channels/[id]/agent-tools (nivel de canal) |
| Autenticación | Sesió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) |
| Registro | Cada 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:
| Campo | Requerido | Notas |
|---|---|---|
| Name | sí | El nombre de función que llama el LLM, p. ej. get_order_status. Debe ser un identificador de función válido |
| Description | sí | El LLM lo lee para decidir cuándo llamar a la herramienta. Máx ~2000 caracteres (validado). Sea específico |
| URL | sí (lado servidor) | Debe ser HTTPS. Protegida contra SSRF: una lista de bloqueo rechaza objetivos de red interna/privada |
| Method | no | GET | POST | PUT | PATCH. Por defecto POST |
| Headers | no | Objeto JSON opcional. Puede cifrarse/enmascararse (úselo para claves de API/tokens) |
| Parameters Schema | sí | Un objeto JSON Schema que describe los argumentos que el LLM rellena (parametersSchema) |
| Type | sí | SERVER_SIDE o CLIENT_SIDE |
| Active | — | Interruptor para activar/desactivar la herramienta sin eliminarla |
Ejemplo práctico — get_order_status (SERVER_SIDE)#
Definición de la herramienta
| Campo | Valor |
|---|---|
| Name | get_order_status |
| Type | SERVER_SIDE |
| Method | POST |
| URL | https://api.YOURCOMPANY.com/orders/status |
| Headers | { "Authorization": "Bearer YOUR_API_TOKEN" } (almacenar como cabecera cifrada) |
| Description | Look 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
descriptionde 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 servidor | Lado cliente | |
|---|---|---|
| Tiempo de espera | ~10s | ~5s |
| Límite de respuesta | 128 KB (rechazada, no truncada) | — |
| En caso de fallo/tiempo de espera | Registrado; 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:
- La IA emite una llamada a herramienta para una herramienta
CLIENT_SIDE. - El servidor emite
tool:executeal widget. - El widget la ejecuta y responde con
tool:result(otool:error). - 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
| Campo | Valor |
|---|---|
| Nombre | where_is_the_user |
| Tipo | CLIENT_SIDE |
| Descripción | Check 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
| Campo | Valor |
|---|---|
| Nombre | highlight |
| Tipo | CLIENT_SIDE |
| Descripción | Scroll 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
| Campo | Valor |
|---|---|
| Nombre | read_section |
| Tipo | CLIENT_SIDE |
| Descripción | Read 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
| Campo | Valor |
|---|---|
| Nombre | fill_field |
| Tipo | CLIENT_SIDE |
| Descripción | Put 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
resultes 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.