package services import ( "fmt" "log" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // DuplicarAgente crea una copia de un agente en el tenant indicado, con su // conocimiento y sus herramientas. // // Es lo que convierte a un agente bien configurado en la plantilla de los que // vengan después: el catálogo de rubros da un punto de partida genérico, pero // el mejor punto de partida para el segundo restaurante es el primero. // // Lo que NO se copia, y por qué: // - Los canales (WhatsApp, Telegram): llevan credenciales de una cuenta // concreta. Copiarlas haría que dos agentes contesten por el mismo número. // - Las conversaciones: son de los clientes del otro negocio. // - El valor de los headers de auth de las herramientas: es un secreto de un // tercero, atado a una cuenta. La herramienta se copia sin él, para que // quien reciba la copia lo cargue. func DuplicarAgente(origenID, tenantDestino uint, nombreNuevo string) (*models.UmindAgente, error) { origen, err := models.GetUmindAgenteByID(origenID) if err != nil { return nil, fmt.Errorf("el agente que querés copiar no existe: %w", err) } if tenantDestino == 0 { tenantDestino = origen.TenantID } if nombreNuevo == "" { nombreNuevo = origen.Nombre + " (copia)" } copia := &models.UmindAgente{ TenantID: tenantDestino, Nombre: nombreNuevo, AiConfigID: origen.AiConfigID, Tono: origen.Tono, MensajeBienvenida: origen.MensajeBienvenida, Color: origen.Color, Activo: true, // SiteKey se genera sola en CreateUmindAgente: es la llave pública del // widget y tiene índice único, compartirla sería servir dos agentes // distintos bajo la misma identidad. } if err := models.CreateUmindAgente(copia); err != nil { return nil, fmt.Errorf("no se pudo crear la copia: %w", err) } go copiarConocimiento(origen.ID, copia.ID) copiarHerramientas(origen.ID, copia.ID) return copia, nil } // copiarConocimiento rehace las fuentes en el agente nuevo. Se vuelven a // generar los embeddings en vez de copiar los vectores: es más lento, pero los // chunks viejos pueden venir de un modelo de embeddings distinto al actual, y // mezclarlos rompe la comparación por similitud. func copiarConocimiento(origenID, destinoID uint) { docs, err := models.GetUmindDocumentosByAgente(origenID) if err != nil { log.Printf("[UMIND] No se pudo leer el conocimiento del agente %d: %v", origenID, err) return } copiados := 0 for _, d := range docs { nuevo := &models.UmindDocumento{ AgenteID: destinoID, Tipo: d.Tipo, Origen: d.Origen, Contenido: d.Contenido, MaxPaginas: d.MaxPaginas, AutoActualizar: d.AutoActualizar, Estado: "procesando", } if err := models.CreateUmindDocumento(nuevo); err != nil { log.Printf("[UMIND] No se pudo copiar la fuente %q: %v", d.Origen, err) continue } switch { case d.Tipo == "url": IngestarAgente(destinoID, nuevo.ID, d.Origen, d.MaxPaginas) case d.Contenido != "": IngestarTexto(destinoID, nuevo.ID, d.Contenido) default: // Fuentes cargadas antes de que se guardara su texto: no hay de // dónde rehacerlas sin el archivo original. _ = models.UpdateUmindDocumentoEstado(nuevo.ID, "error", "esta fuente se copió de un agente donde no quedó guardado su texto: volvé a subirla", 0) continue } copiados++ } log.Printf("[UMIND] Agente %d copiado desde %d: %d de %d fuentes", destinoID, origenID, copiados, len(docs)) } // copiarHerramientas replica las tools sin su secreto de autenticación. func copiarHerramientas(origenID, destinoID uint) { tools, err := models.GetUmindHerramientasByAgente(origenID) if err != nil { log.Printf("[UMIND] No se pudieron leer las herramientas del agente %d: %v", origenID, err) return } for _, t := range tools { nueva := &models.UmindHerramienta{ AgenteID: destinoID, Nombre: t.Nombre, Descripcion: t.Descripcion, ParametrosJSON: t.ParametrosJSON, URL: t.URL, AuthHeaderNombre: t.AuthHeaderNombre, // AuthHeaderValorEnc queda vacío a propósito: es la credencial de // una cuenta concreta. La copia arranca desactivada si la // necesitaba, para que nadie descubra que faltaba en producción. Activa: t.Activa && (t.AuthHeaderNombre == "" || t.AuthHeaderValorEnc == ""), } if err := models.CreateUmindHerramienta(nueva); err != nil { log.Printf("[UMIND] No se pudo copiar la herramienta %q: %v", t.Nombre, err) } } }