Agrega uMind: chat con IA embebible por tenant (F1 — widget web + RAG)

Multi-tenant dentro de soft_usite, reutilizando la infraestructura ya
existente (AiConfig, motor de function-calling del agente de Telegram)
en vez de un servicio nuevo aparte:

- UmindTenant: sitio/cliente con dominios permitidos, config de IA para
  el chat y personalidad/tono.
- Ingesta: crawler simple (mismo dominio, N páginas) + chunking +
  embeddings (config global con módulo "umind_embeddings", pensada
  para OpenAI ya que Claude no ofrece embeddings) guardados como JSON,
  con búsqueda por similitud coseno en memoria (sin pgvector todavía).
- Agente acotado: única herramienta buscar_conocimiento, sin acceso a
  nada interno — si no encuentra la respuesta, lo dice en vez de
  inventar.
- Widget público (/widget/umind.js + /widget/:site_key/*), autenticado
  por site_key + validación de dominio (Origin/Referer), no por
  secreto, ya que la key viaja en el HTML público del sitio instalado.
- Panel /app/umind: tenants, estado de ingesta, historial de
  conversaciones por sesión.
This commit is contained in:
Lizandro GD
2026-08-07 16:18:16 +00:00
parent 0eef3f80a3
commit 5ee5880dc2
17 changed files with 1806 additions and 4 deletions
+23
View File
@@ -194,3 +194,26 @@ func GetWhisperConfig() (*AiConfig, error) {
}
return nil, fmt.Errorf("no hay ninguna configuración activa con el módulo 'whisper' en /app/ai-config")
}
// GetUmindEmbeddingsConfig retorna la config activa etiquetada con el módulo
// "umind_embeddings" (usada para generar los vectores de la base de
// conocimiento de todos los tenants de uMind). Es global, no por tenant: los
// embeddings de un tenant solo son comparables entre sí si se generaron con
// el mismo modelo, así que cambiar de config invalida los chunks existentes
// (habría que reingestar). Sin fallback, igual que GetWhisperConfig — Claude
// no ofrece embeddings, así que aquí sí importa exigir una config explícita
// en vez de caer a cualquier config activa.
func GetUmindEmbeddingsConfig() (*AiConfig, error) {
var items []AiConfig
if err := app.Http.Database.DB.Where("is_active = ?", true).Find(&items).Error; err != nil {
return nil, fmt.Errorf("error leyendo ai_configs: %w", err)
}
for i := range items {
for _, m := range SplitModulos(items[i].Modulo) {
if m == "umind_embeddings" {
return &items[i], nil
}
}
}
return nil, fmt.Errorf("no hay ninguna configuración activa con el módulo 'umind_embeddings' en /app/ai-config (necesaria para generar embeddings, ej. un proveedor OpenAI)")
}