Saltar al contenido principal
Dos formas de darle al plugin de IA más contexto que el historial del chat:
  • Memoria a largo plazo: recuerda hechos cortos de cada persona (“prefiere español”, “vive en Bogotá”) entre conversaciones y reinicios, y los inyecta en el autoresponder.
  • RAG (Retrieval Augmented Generation): busca los pasajes de tus documentos (FAQ, catálogo, manual) que responden a la pregunta y los inyecta numerados, para que el modelo responda a partir de ellos y los cite.
Ambos alimentan el prompt de sistema de replyInChat() (y por tanto del autoresponder); ninguno hace fallar una respuesta: si fallan o tardan demasiado, el turno sigue sin ellos.

Memoria a largo plazo

Dónde viven los hechos

Los hechos se guardan en el perfil persistente del usuario, ctx.user, en el namespace 'ai.memory' (clave facts), por el id canónico del remitente (ctx.sender). Así:
  • La misma persona tiene una sola memoria entre su chat privado y los grupos en los que escribe, y entre reinicios si el store es persistente (MemoryStore vive en RAM).
  • Necesita un store con soporte de datos de usuario (todos los stores incluidos lo tienen).
  • bot.deleteSubject(userId) la borra junto con el resto del perfil.
  • /reset borra el contexto de IA del chat, no la memoria a largo plazo.

Qué se inyecta

Con memory activo, los hechos del usuario se añaden al prompt de sistema de cada turno del autoresponder, como datos y no como instrucciones. Los grupos quedan fuera por defecto: todo el grupo lee la respuesta, así que los hechos aprendidos en privado podrían filtrarse. Activa injectInGroups: true solo si eso es aceptable.

Extracción automática

Con autoExtract, después de cada turno el plugin le pide al modelo, en segundo plano (nunca bloquea la cola del chat), hechos duraderos del usuario. La respuesta debe ser exactamente {"facts": string[]}: cualquier otra cosa (claves extra, prosa, elementos que no son strings) se descarta entera, así que un modelo confundido o con prompt injection no puede escribir datos arbitrarios en el perfil. El prompt también le indica que omita secretos y datos sensibles (contraseñas, números de tarjeta o cuenta, documentos de identidad, datos de salud). Si se borran los datos del usuario (bot.deleteSubject() o forget(userId)) mientras una extracción está en curso, sus hechos se descartan: los datos borrados nunca vuelven. autoExtract acepta true o un objeto:

Opciones de memory

memory: true usa los valores por defecto.

Leer y escribir la memoria

ctx.tools.ai expone la API de memoria. Funciona incluso sin la opción memory (en ese caso no se inyecta ni se extrae nada automáticamente, pero tu código puede leer y escribir hechos).

RAG

rag acepta cualquier retriever, un objeto con retrieve(query, { topK?, minScore?, signal? }) que resuelve pasajes { text, source?, citation?, score? }. El paquete @botmesh/rag de más abajo trae uno con loaders, chunking, embeddings e índice; una base de datos vectorial o una API de búsqueda sirven igual.
En cada turno el plugin recupera pasajes para el mensaje del usuario y los inyecta en el prompt de sistema como un bloque numerado ([1] (etiqueta) texto), con instrucciones de usarlos cuando sean relevantes, citarlos como [n], decir que no lo sabe si el contexto no tiene la respuesta y tratar el contexto como datos, no como instrucciones. rag: retriever (el retriever a secas) equivale a rag: { retriever }. La línea por defecto de appendSources es el mensaje localizado ai.rag.sources (Sources: {sources} por defecto, Fuentes: {sources} cuando ctx.lang es es); cámbiala con bot.locale() o pasa una función. Al instalarse, el plugin llama al warmup() opcional del retriever (no fatal: un fallo se registra en el log), así que el índice se construye al arrancar y no en la primera pregunta.

Citas y hechos de un turno

replyInChatDetailed() devuelve lo que usó el turno:
replyInChat(key, text, { userId, isGroup }) devuelve solo el texto. Pasa userId para activar la memoria en el turno, e isGroup para que los hechos no lleguen a las respuestas en grupos. Con streaming, la memoria y el RAG funcionan igual; con appendSources, la línea de fuentes llega como último fragmento.

@botmesh/rag

No depende de @botmesh/ai: aiPlugin acepta cualquier retriever y createRag() devuelve uno. createRag(options) construye un retriever sin base de datos vectorial: un índice coseno en memoria con persistencia opcional en JSON.
  • Loaders: Markdown (por secciones de encabezados), texto, JSON (un documento por elemento de un array) y PDF (con el peer opcional pdf-parse o tu propio parser pdf). Los directorios se leen de forma recursiva.
  • Chunking: párrafos, luego frases, luego palabras, con solapamiento.
  • Embeddings: 'local' (por defecto; hashing determinista, sin red, solo léxico, ideal para tests y FAQs pequeñas), 'openai' (cualquier endpoint /embeddings compatible con OpenAI; por defecto text-embedding-3-small y OPENAI_API_KEY), 'ollama' (por defecto nomic-embed-text en http://localhost:11434), un objeto { provider, ... } o tu propio Embedder ({ id, embed(texts) }). Las peticiones salen por secureFetch de @botmesh/http.
  • Índice: topK, minScore y filtros. Con persistPath se guarda en JSON tras cada cambio y, al reiniciar, solo se vuelven a embeber los fragmentos cuyo texto cambió. Si cambias de embedder (otro id), los documentos añadidos en tiempo de ejecución y un índice precompilado se vuelven a embeber con él en lugar de perderse; onError recibe un RagError('EMBEDDER_CHANGED') y el archivo anterior se conserva como <persistPath>.previous. Si el índice no se puede guardar al cargarlo (archivo de solo lectura), onError recibe PERSIST_FAILED y se sirve desde memoria.
createRag() es síncrono y barato, así que cabe en un install síncrono de plugin. También puedes usarlo directamente:
Todo fallo es un RagError con un code: INVALID_OPTIONS, UNSUPPORTED_FORMAT, LOAD_FAILED, MISSING_DEPENDENCY, EMBEDDING_FAILED, INVALID_INDEX o PERSIST_FAILED (EMBEDDER_CHANGED solo llega a onError). El índice es de fuerza bruta, pensado para miles de fragmentos; para millones, escribe tu propio retriever sobre una base vectorial. Detalles en el README de @botmesh/rag.

Testing

MockAIProvider no necesita red. Para probar la memoria, dale al AIClient un MemoryManager sobre los perfiles del bot; para probar el RAG, sirve cualquier objeto con retrieve():

Siguientes pasos