- 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.
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
(
MemoryStorevive 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./resetborra el contexto de IA del chat, no la memoria a largo plazo.
Qué se inyecta
Conmemory 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
ConautoExtract, 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.
[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
@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-parseo tu propio parserpdf). 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/embeddingscompatible con OpenAI; por defectotext-embedding-3-smallyOPENAI_API_KEY),'ollama'(por defectonomic-embed-textenhttp://localhost:11434), un objeto{ provider, ... }o tu propioEmbedder({ id, embed(texts) }). Las peticiones salen porsecureFetchde@botmesh/http. - Índice:
topK,minScorey filtros. ConpersistPathse guarda en JSON tras cada cambio y, al reiniciar, solo se vuelven a embeber los fragmentos cuyo texto cambió. Si cambias de embedder (otroid), los documentos añadidos en tiempo de ejecución y un índice precompilado se vuelven a embeber con él en lugar de perderse;onErrorrecibe unRagError('EMBEDDER_CHANGED')y el archivo anterior se conserva como<persistPath>.previous. Si el índice no se puede guardar al cargarlo (archivo de solo lectura),onErrorrecibePERSIST_FAILEDy 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:
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
- Plugin de IA — proveedores y la referencia completa de
aiPlugin - Herramientas de IA — deja que el modelo llame a funciones de tu bot
- Datos por usuario — el perfil
ctx.userdonde vive la memoria