Memoria del bot

El agente IA recuerda tres cosas: el historial de la conversación, los atributos del contacto y la memoria de la organización: una biblioteca de documentos que tú alimentas con archivos, notas escritas a mano y páginas web. Cada agente elige qué parte de esa biblioteca consulta, y antes de responder busca en ella los fragmentos que hablan de lo que preguntó el cliente.

Referencia completa de endpoints en Bot · Knowledge. Permisos: bot:read para consultar y buscar, bot:write para crear, editar o borrar fuentes, bot:ai-config para decidir qué documentos usa cada agente.

Cómo funciona (RAG en un minuto)

  1. Registras una fuente: un archivo, una nota o la URL de una página pública.
  2. Un worker extrae el texto, lo parte en fragmentos de ~700 tokens con solapamiento y genera un embedding por fragmento (text-embedding-3-small, 1536 dimensiones). Los fragmentos también se indexan a texto completo en español.
  3. El documento pasa de PENDING a READY (o FAILED con un mensaje que dice qué corregir).
  4. Cuando un cliente escribe, el agente busca los top-K fragmentos más relevantes entre los documentos que tiene asignados y se los pasa al modelo como contexto del negocio.
  5. Si lo que el cliente pregunta no está en la memoria, la consulta queda registrada como vacío de conocimiento para que lo cubras después.

Fuentes

FuenteEndpointNotas
FILEPOST /organizations/{orgId}/bot/knowledgeMultipart: file + opcional title y tags (separados por coma). PDF, DOCX, TXT, MD y CSV, hasta 25 MiB.
NOTEPOST …/bot/knowledge/notes
PATCH …/bot/knowledge/notes/{id}
Texto escrito en el panel o por API (title, content en Markdown, tags). Al editar el contenido se vuelve a procesar. resolvesQuestionKey marca qué vacío cierra.
URLPOST …/bot/knowledge/urlPágina web pública (url, title, tags). Con syncEveryHours (1–720) se vuelve a leer sola; sin él, solo cuando la reproceses.
# Archivo (multipart)
curl -X POST "https://api.mosend.dev/organizations/${ORG_ID}/bot/knowledge" \
  -H "X-Api-Key: ${API_KEY}" \
  -F "file=@politica-de-devoluciones.pdf" \
  -F "title=Política de devoluciones" \
  -F "tags=soporte,devoluciones"

# Nota escrita a mano
curl -X POST "https://api.mosend.dev/organizations/${ORG_ID}/bot/knowledge/notes" \
  -H "X-Api-Key: ${API_KEY}" -H "Content-Type: application/json" \
  -d '{
    "title": "Horario de despachos",
    "content": "Despachamos de lunes a viernes antes de las 3 p. m. Los pedidos del fin de semana salen el lunes.",
    "tags": ["envios"]
  }'

# Página web que se relee cada 24 horas
curl -X POST "https://api.mosend.dev/organizations/${ORG_ID}/bot/knowledge/url" \
  -H "X-Api-Key: ${API_KEY}" -H "Content-Type: application/json" \
  -d '{ "url": "https://tutienda.com/preguntas-frecuentes", "title": "FAQ del sitio", "syncEveryHours": 24 }'

Los tres responden con el documento recién creado (status: "PENDING"). El procesamiento corre en segundo plano; consulta GET …/bot/knowledge/{id} hasta verlo en READY.

Estado de procesamiento y resincronización

  • PENDING → en cola o procesándose. READY → indexado y en uso (chunkCount dice cuántos fragmentos). FAILED → no se pudo; el campo error explica qué corregir (PDF escaneado, URL que no responde, tipo no permitido…).
  • POST …/bot/knowledge/{id}/reprocess vuelve a extraer el texto y regenera los embeddings. Sirve para archivos y páginas por igual.
  • Las páginas con syncEveryHours las revisa un proceso cada hora: la que ya cumplió su intervalo se descarga de nuevo y solo se reprocesa si cambió el contenido (se compara un hash del texto). Si la página no cambió, no gasta embeddings.
  • PATCH …/{id}/title y PATCH …/{id}/tags renombran y re-etiquetan sin reprocesar. DELETE …/{id} borra fragmentos, vectores y el archivo.

Qué agente consulta qué

La biblioteca es de la organización; cada agente decide qué parte lee con knowledgeDocIds en POST/PATCH /organizations/{orgId}/bot/agents — en el panel son casillas por documento.

  • Vacío → el agente consulta toda la biblioteca.
  • Con ids → solo esos documentos, sin importar sus etiquetas.
  • knowledgeTags (el mecanismo anterior, por etiqueta) sigue funcionando, pero con menor precedencia: si el agente tiene knowledgeDocIds, las etiquetas no se miran.
curl -X PATCH "https://api.mosend.dev/organizations/${ORG_ID}/bot/agents/${AGENT_ID}" \
  -H "X-Api-Key: ${API_KEY}" -H "Content-Type: application/json" \
  -d '{ "knowledgeDocIds": ["<docId-devoluciones>", "<docId-faq>"], "knowledgeTopK": 5, "knowledgeMinSimilarity": 0.3 }'

knowledgeTopK (default 5) es cuántos fragmentos entran al contexto por turno; knowledgeMinSimilarity (default 0.3) el parecido mínimo para usar uno. Ambos se fijan por agente.

Cómo recupera: búsqueda híbrida

La recuperación combina dos rankings sobre los mismos fragmentos y los fusiona con Reciprocal Rank Fusion:

  • Parecido semántico: distancia coseno entre el embedding de la pregunta y el de cada fragmento (pgvector). Entiende sinónimos y paráfrasis.
  • Palabras exactas: texto completo en español (tsvector). Encuentra un código de producto, un nombre propio o una sigla que el embedding diluye.

Un fragmento hallado por palabra exacta (porTexto: true) se usa aunque su parecido quede por debajo de knowledgeMinSimilarity: si el cliente escribe la referencia exacta, el bot la encuentra aunque el resto de la frase no se parezca a nada del documento.

Probar la búsqueda

POST /organizations/{orgId}/bot/knowledge/searchejecuta exactamente la misma consulta que hace el bot antes de responder. Úsalo para comprobar qué vería el agente ante una pregunta, o como "buscar en el conocimiento de la empresa" desde tu propio sistema.

curl -X POST "https://api.mosend.dev/organizations/${ORG_ID}/bot/knowledge/search" \
  -H "X-Api-Key: ${API_KEY}" -H "Content-Type: application/json" \
  -d '{ "query": "¿cuánto tarda una devolución?", "topK": 5, "tags": ["soporte"], "minSimilarity": 0.3 }'

# Respuesta (200)
{
  "data": {
    "results": [
      {
        "docId": "…",
        "title": "Política de devoluciones",
        "content": "El reembolso se procesa en 5 a 8 días hábiles desde que recibimos el producto…",
        "similarity": 0.61,
        "porTexto": true
      }
    ]
  }
}
  • topK 1–20 (default 5). tags limita a documentos con alguna de esas etiquetas. minSimilarity 0–1 (default 0.3).
  • Sin documentos en READY devuelve results: [] sin gastar embedding. Cada búsqueda con documentos cuesta un embedding de la pregunta (se registra en AiUsage).

Vacíos de conocimiento (insights)

Cada vez que el bot busca en la memoria queda registrado qué preguntó, si encontró algo y qué documentos usó. GET /organizations/{orgId}/bot/knowledge/insights?dias=30 lo resume:

{
  "data": {
    "desde": "2026-08-08T…", "dias": 30,
    "resumen": { "consultas": 412, "respondidas": 371, "sinRespuesta": 41, "tasa": 90, "documentos": 12, "documentosListos": 11 },
    "vacios": [
      { "questionKey": "garantia-extendida", "question": "¿tienen garantía extendida?", "veces": 9,
        "ultimaVez": "2026-09-06T…", "agentIds": ["…"], "agentes": ["Ventas"] }
    ],
    "porAgente": [ { "agentId": "…", "nombre": "Ventas", "consultas": 300, "respondidas": 281 } ],
    "documentos": [ { "id": "…", "title": "FAQ del sitio", "sourceType": "URL", "status": "READY", "useCount": 140, "lastUsedAt": "…", "usosEnPeriodo": 88 } ]
  }
}
  • vacios agrupa las preguntas que se quedaron sin respuesta por su forma normalizada (questionKey), con cuántas veces se repitió y qué agentes la recibieron.
  • Para cerrar un vacío, crea una nota con ese resolvesQuestionKey: el grupo deja de aparecer y la próxima vez el bot ya tiene el dato.
  • documentos ordena la biblioteca por uso en el período: lo que nunca se usa es candidato a revisar o borrar.

Flujo típico

  1. Carga las fuentes base: el PDF de políticas, una nota con horarios y datos que cambian seguido, y la FAQ pública como URL con syncEveryHours: 24.
  2. Espera a que queden en READY (GET …/bot/knowledge). Si alguna cae en FAILED, lee error y corrígela.
  3. Asigna documentos por agente con knowledgeDocIds: soporte lee políticas y FAQ; ventas lee el catálogo.
  4. Prueba con POST …/bot/knowledge/search las 5 preguntas que más recibes. Si el fragmento correcto no aparece, reescribe esa parte del documento en lenguaje natural.
  5. Cada semana revisa GET …/bot/knowledge/insights?dias=7 y cierra los vacíos con notas.

Formatos, límites y buenas prácticas

  • PDF con texto seleccionable (no escaneado), DOCX, TXT/MD y CSV (cada fila se convierte en "Columna: valor · Columna: valor", ideal para listas de precios). Máximo 25 MiB por archivo.
  • Páginas web: solo URLs públicas; se guarda el texto visible, sin menús ni scripts.
  • Documentos cortos y temáticos funcionan mejor que un manual gigante: la recuperación es por fragmento, no por archivo.
  • Una pregunta, una sección, en lenguaje natural: "El plazo de devolución es de 30 días" se recupera mejor que "Devol: 30d".
  • Lo que cambia seguido va en notas: se editan sin volver a subir un archivo, y una nota con resolvesQuestionKey cierra un vacío de una vez.

Errores típicos

  • El PDF parece una imagen escaneada: no tiene texto seleccionable. Pásalo por OCR y vuelve a subirlo.
  • El bot no usa mi documento: confirma que el agente tenga ese documento en knowledgeDocIds (o la lista vacía), que el documento esté en READY, y que el número esté en un modo con IA (AI_AGENT o RULES_PLUS_AI_FALLBACK). Luego reproduce la pregunta con /search.
  • El bot dice cosas viejas: reprocesa el documento o, si es una página, revisa que tenga syncEveryHours.
  • Quiero sacar un documento de un agente sin borrarlo: quítalo de sus knowledgeDocIds; sigue disponible para los demás.

Costos

Procesar usa embeddings (text-embedding-3-small, USD 0.02 por millón de tokens); cada turno del bot y cada llamada a /search suman un embedding de la pregunta. Todo se cobra del saldo de la organización con el margen configurado y queda en AiUsage. Como referencia: 50 PDF de 10 páginas rondan USD 0.03 en total, una sola vez.