Agentes del bot (orquestación)

En vez de un solo bot que lo sabe todo, una organización tiene agentes especializados: ventas, soporte, licencias, agenda… cada uno con su prompt, su modelo, sus herramientas y su parte de la memoria. Esta guía explica cómo se crean, quién atiende cada conversación y cómo se pasan la conversación entre ellos sin que el cliente lo note.

Referencia de endpoints en Bot · Agentes especializados. Permisos: bot:read para listar y ver estadísticas, bot:ai-config para crear, editar o borrar agentes, conversations:write para fijar el agente de una conversación.

Endpoints

RutaQué hace
GET /organizations/{orgId}/bot/agentsLista los agentes. ?phoneNumberId= deja solo los que atienden ese canal.
POST /organizations/{orgId}/bot/agentsCrea un agente (name obligatorio; el resto tiene defaults).
GET · PATCH · DELETE /organizations/{orgId}/bot/agents/{id}Detalle, edición parcial y borrado.
GET /organizations/{orgId}/bot/agents/capabilitiesCatálogo de capacidades activables (label, módulo que requiere).
GET /organizations/{orgId}/bot/agents/modelosProveedores con sus modelos elegibles y si la organización tiene clave.
GET /organizations/{orgId}/bot/agents/stats?dias=Rendimiento por agente en los últimos N días.
POST /organizations/{orgId}/bot/agents/{agentId}/probadorConversa con el agente sin enviar nada al cliente: devuelve la respuesta y las herramientas que usaría.
POST /organizations/{orgId}/bot/agents/{agentId}/configuradorPropone cambios al prompt conversando; no guarda nada.
PATCH /organizations/{orgId}/conversations/{id}/agentFija a mano qué agente atiende esa conversación (agentId null = que decida el enrutador).
POST /organizations/{orgId}/conversations/{id}/devolver-al-botReactiva el bot en una conversación traspasada, conservando el contexto.

Campos de un agente

CampoPara qué sirve
name, descriptionNombre visible en el panel y en las estadísticas; la descripción es la que lee el router IA para decidir a quién entregar.
systemPromptInstrucciones del agente (hasta 32 000 caracteres). Los precios y enlaces van mejor en el catálogo de servicios que aquí.
provider, modelProveedor (anthropic · openai · openrouter · groq · meta) y modelo. GET …/bot/agents/modelos lista los elegibles y si hay clave configurada.
temperature, maxTokensParámetros de generación. Los modelos que los deprecaron (Claude 5.x, gpt-5.x) los ignoran sin fallar.
enabledToolsHerramientas que el agente puede invocar (transfer_to_human, set_contact_attribute, transfer_to_agent…).
enabledCapabilitiesCapacidades de negocio (ventas, agenda, licencias, soporte…). Catálogo en GET …/bot/agents/capabilities; las que el plan no incluye dan 400 al activarlas.
routeTagsEtiquetas de conversación o contacto que hacen que este agente atienda. Una etiqueta solo puede pertenecer a un agente.
knowledgeDocIds, knowledgeTopK, knowledgeMinSimilarityQué documentos de la memoria lee (vacío = toda la biblioteca), cuántos fragmentos por turno y el parecido mínimo. knowledgeTags sigue vigente con menor precedencia.
phoneNumberIdsCanales en los que atiende: ids de números reales o virtuales (web chat, Instagram). Vacío = todos los canales de la organización. phoneNumberId (uno solo) es el atajo legado.
isDefaultAgente que toma la conversación cuando nada más decide. Solo puede haber uno por canal: si otro ya es el predeterminado de ese número, la API responde 400.
enabled, sortOrderUn agente apagado no recibe conversaciones nuevas ni traspasos. sortOrder ordena la lista y desempata entre etiquetas.
curl -X POST "https://api.mosend.dev/organizations/${ORG_ID}/bot/agents" \
  -H "X-Api-Key: ${API_KEY}" -H "Content-Type: application/json" \
  -d '{
    "name": "Soporte",
    "description": "Resuelve dudas de uso, garantías y devoluciones de clientes que ya compraron.",
    "systemPrompt": "Eres el asesor de soporte de Tienda X. Responde corto y en español neutro…",
    "provider": "anthropic",
    "model": "claude-sonnet-4-5",
    "enabledTools": ["transfer_to_human", "transfer_to_agent"],
    "routeTags": ["soporte", "garantia"],
    "knowledgeDocIds": ["<docId-politicas>", "<docId-faq>"],
    "phoneNumberIds": [],
    "isDefault": false,
    "enabled": true
  }'

Canales

phoneNumberIds dice en qué canales atiende el agente. Los ids son los de phone-numbers, incluidos los números virtuales que representan un canal de web chat o una cuenta de Instagram. Con la lista vacía el agente atiende en todos. Un agente que no está en el canal de la conversación no puede recibirla: ni por etiqueta, ni por traspaso, ni por el router.

Quién atiende: precedencia del enrutamiento

En cada turno el bot decide el agente en este orden; el primero que aplica gana:

  1. Agente activo de la conversación (activeAgentId): lo fijó un traspaso, el router o un asesor a mano. Se respeta mientras siga habilitado y en ese canal.
  2. Etiquetas: si la conversación o el contacto tiene una etiqueta que algún agente reclama en routeTags, ese agente empieza a atender. Sin distinguir mayúsculas; gana el primero por isDefaultsortOrder → fecha de creación.
  3. Router IA: solo en el primer contacto y con dos o más agentes en el canal. Un modelo lee el mensaje y las descripciones de los agentes y elige uno.
  4. Agente por defecto del canal (isDefault) si nada de lo anterior decidió.
  • Cuando el agente cambia queda un evento AGENT_ROUTED en bot-events con via: tag | router | default.
  • La etiqueta decide solo al empezar o cuando el activo dejó de estar vigente: no deshace un traspaso. Antes sí lo hacía y la conversación rebotaba entre dos agentes en el mismo mensaje.
  • Una etiqueta → un agente. Si intentas dar a un agente una routeTag que otro ya tiene, la API responde 400: «La etiqueta «vip» ya activa al agente «Ventas». Quítasela a uno de los dos.»

Traspasos entre agentes

Con la herramienta transfer_to_agent habilitada, un agente puede entregar la conversación a un hermano (por ejemplo, ventas detecta un reclamo y lo pasa a soporte). El traspaso es invisible para el cliente: el agente destino responde en el mismo turno, sin "te paso con…" ni silencio. Reglas:

  • El destino debe ser un agente real, distinto, habilitado y presente en el canal; cualquier otra cosa se ignora y el agente actual sigue respondiendo.
  • Máximo un traspaso por mensaje (evita el rebote A → B → A).
  • Queda registrado como AGENT_TRANSFERRED y cuenta en las estadísticas como traspaso saliente de uno y entrante del otro.

Fijar o soltar el agente a mano

Desde el inbox (o tu propio sistema) puedes decidir quién atiende, sin esperar al enrutador:

# Que Soporte atienda esta conversación desde ahora
curl -X PATCH "https://api.mosend.dev/organizations/${ORG_ID}/conversations/${CONVERSATION_ID}/agent" \
  -H "X-Api-Key: ${API_KEY}" -H "Content-Type: application/json" \
  -d '{ "agentId": "<agentId-soporte>" }'

# Soltar: que el enrutador vuelva a decidir en el próximo mensaje
curl -X PATCH "https://api.mosend.dev/organizations/${ORG_ID}/conversations/${CONVERSATION_ID}/agent" \
  -H "X-Api-Key: ${API_KEY}" -H "Content-Type: application/json" \
  -d '{ "agentId": null }'

# Tras un traspaso a humano, devolver la conversación al bot con su contexto
curl -X POST "https://api.mosend.dev/organizations/${ORG_ID}/conversations/${CONVERSATION_ID}/devolver-al-bot" \
  -H "X-Api-Key: ${API_KEY}"

Fijar el agente no reactiva un bot silenciado por handoff: para eso está devolver-al-bot. Y al revés, devolver-al-bot conserva el agente activo que había.

Entregar a un agente desde un flujo

El paso transfer_to_ai de los flujos termina el flujo y deja la conversación en manos de la IA. Con agentId eliges cuál; sin él, el que corresponda por la precedencia de arriba.

{
  "type": "transfer_to_ai",
  "message": "Te dejo con nuestro asesor de ventas.",
  "agentId": "<agentId-ventas>"
}

Rendimiento por agente

GET /organizations/{orgId}/bot/agents/stats?dias=30 atribuye a cada agente lo que hizo en el período:

{
  "data": {
    "desde": "2026-08-08T…", "dias": 30,
    "agentes": [
      {
        "agentId": "…", "name": "Ventas", "enabled": true, "isDefault": true,
        "channels": [ { "phoneNumberId": "…", "displayPhoneNumber": "+57 3xx…" } ],
        "conversaciones": 212, "respuestas": 1480,
        "traspasosSalientes": 31, "traspasosEntrantes": 4, "escalamientosAHumano": 18,
        "ventas": 27, "citas": 0, "fallos": 2, "fallosDeModelo": 3,
        "tokensInput": 1900000, "tokensOutput": 210000, "costUsd": 9.4, "chargedUsd": 14.1,
        "latenciaPromedioMs": 2900,
        "porCanal": [ { "phoneNumberId": "…", "label": "+57 3xx…", "respuestas": 1480 } ],
        "ultimosFallos": [ { "at": "…", "conversationId": "…", "error": "…" } ]
      }
    ],
    "sinAtribuir": { "agentId": null, "name": "Sin atribuir (router, flujos o anterior a la medición)", "respuestas": 40, "…": "…" }
  }
}
  • sinAtribuir agrupa lo que no tiene agente: el router IA, los flujos y las filas anteriores a que existiera la atribución. Es null si no hay nada huérfano.
  • costUsd es el costo real del proveedor; chargedUsd lo que se descontó del saldo (con margen).

Memoria por agente

La biblioteca de documentos es una por organización; cada agente lee la parte que le asignes con knowledgeDocIds. Cómo cargar fuentes, probar la búsqueda y cerrar vacíos está en Memoria del bot.

Flujo típico

  1. Crea el agente general y márcalo isDefault en el canal principal.
  2. Crea los especializados con una description clara (es lo que lee el router) y sus routeTags: soporte, reseller, vip
  3. Reparte la memoria: cada agente con sus knowledgeDocIds.
  4. Habilita transfer_to_agent en los que deban derivar y prueba los cruces con POST …/{agentId}/probador.
  5. Etiqueta contactos desde tu CRM (tags) para que el enrutamiento por etiqueta haga el trabajo sin pasar por el router.
  6. Mira stats?dias=7 cada semana: muchos escalamientosAHumano o fallos en un agente señalan un prompt o una capacidad que falta.

Notas

  • El agente resuelve solo en los modos con IA del número (AI_AGENT o RULES_PLUS_AI_FALLBACK en bot-config).
  • Fuera del horario de atención, un pedido de asesor que nace del bot no traspasa: crea una tarea para el equipo y el agente sigue atendiendo. Ver Handoff a humano y Tareas.