feat(umind): cargar conocimiento de tres formas y que deje de quedar viejo en silencio
Hasta ahora la única forma de darle información a un agente era crawlear una
URL, una vez, para siempre. Tres cosas cambian:
Escribir a mano. Es la fuente más valiosa y la única que no está en ningún
documento: horarios, qué no hacen, la respuesta que dan quince veces por día.
También es la única que el dueño puede corregir en el momento en que ve al
agente contestar mal.
Subir un archivo. La lista de precios suele estar en un PDF, no en la web. Usa
el mismo extractor que los adjuntos de los canales, así que PDF, Word, texto e
imágenes entran sin código nuevo. El texto se extrae con la persona mirando la
pantalla: si el archivo no se puede leer, se dice ahí y no en un log.
Y lo importante: el conocimiento se congelaba el día que se cargaba. Si el
cliente cambiaba los precios en su sitio, el agente seguía dando los viejos con
total seguridad — sin error, sin aviso, nada. Ahora cada fuente muestra de
cuándo es ("leído hace 3 meses", en ámbar pasados dos meses), tiene botón de
actualizar, y las URLs pueden marcarse para releerse solas cada semana (cron a
las 4 AM). Reprocesar reemplaza los fragmentos en vez de sumarlos: si no,
quedaban las dos versiones compitiendo en la búsqueda y podía ganar la vieja.
De paso, el troceado dejaba fragmentos que arrancaban a mitad de palabra
("alabra…") porque el solape no se alineaba a un espacio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3ee83c8534
commit
3ea17b0980
+44
-4
@@ -131,11 +131,23 @@ func (t *UmindTenant) DominioPermitido(host string) bool {
|
||||
type UmindDocumento struct {
|
||||
gorm.Model
|
||||
AgenteID uint `json:"agente_id" gorm:"column:agente_id;index"`
|
||||
Tipo string `json:"tipo" gorm:"column:tipo;size:20"` // url | archivo
|
||||
Origen string `json:"origen" gorm:"column:origen;type:text"` // la URL crawleada, o el nombre del archivo
|
||||
Tipo string `json:"tipo" gorm:"column:tipo;size:20"` // url | archivo | texto
|
||||
Origen string `json:"origen" gorm:"column:origen;type:text"` // la URL crawleada, el nombre del archivo, o el título del texto
|
||||
Estado string `json:"estado" gorm:"column:estado;default:'pendiente'"` // pendiente | procesando | listo | error
|
||||
Error string `json:"error" gorm:"column:error;type:text"`
|
||||
TotalChunks int `json:"total_chunks" gorm:"column:total_chunks;default:0"`
|
||||
// Contenido guarda el texto de las fuentes que no se pueden volver a
|
||||
// buscar solas (lo que escribió el dueño, lo que se extrajo de un archivo).
|
||||
// Sin esto no se puede editar ni reprocesar sin volver a subir el archivo.
|
||||
Contenido string `json:"contenido" gorm:"column:contenido;type:text"`
|
||||
// MaxPaginas se guarda para poder recrawlear igual que la primera vez.
|
||||
MaxPaginas int `json:"max_paginas" gorm:"column:max_paginas;default:0"`
|
||||
// ProcesadoAt dice de cuándo es el conocimiento. Una web cambia y el agente
|
||||
// sigue contestando lo viejo con total seguridad: esta fecha es lo único
|
||||
// que delata que la fuente quedó vieja.
|
||||
ProcesadoAt *time.Time `json:"procesado_at" gorm:"column:procesado_at"`
|
||||
// AutoActualizar deja que el cron la vuelva a procesar sola.
|
||||
AutoActualizar bool `json:"auto_actualizar" gorm:"column:auto_actualizar;default:false"`
|
||||
}
|
||||
|
||||
func (UmindDocumento) TableName() string { return "umind_documentos" }
|
||||
@@ -159,11 +171,32 @@ func GetUmindDocumentoByID(id uint) (*UmindDocumento, error) {
|
||||
}
|
||||
|
||||
func UpdateUmindDocumentoEstado(id uint, estado, errMsg string, totalChunks int) error {
|
||||
return app.Http.Database.DB.Model(&UmindDocumento{}).Where("id = ?", id).Updates(map[string]interface{}{
|
||||
updates := map[string]interface{}{
|
||||
"estado": estado,
|
||||
"error": errMsg,
|
||||
"total_chunks": totalChunks,
|
||||
}).Error
|
||||
}
|
||||
if estado == "listo" {
|
||||
ahora := time.Now()
|
||||
updates["procesado_at"] = &ahora
|
||||
}
|
||||
return app.Http.Database.DB.Model(&UmindDocumento{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
|
||||
func UpdateUmindDocumento(id uint, updates map[string]interface{}) error {
|
||||
return app.Http.Database.DB.Model(&UmindDocumento{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
|
||||
// GetDocumentosParaRefrescar devuelve las fuentes con auto-actualización que no
|
||||
// se procesan desde hace más de los días indicados.
|
||||
func GetDocumentosParaRefrescar(dias int) ([]UmindDocumento, error) {
|
||||
var items []UmindDocumento
|
||||
corte := time.Now().AddDate(0, 0, -dias)
|
||||
err := app.Http.Database.DB.
|
||||
Where("auto_actualizar = ? AND estado <> ?", true, "procesando").
|
||||
Where("procesado_at IS NULL OR procesado_at < ?", corte).
|
||||
Find(&items).Error
|
||||
return items, err
|
||||
}
|
||||
|
||||
func DeleteUmindDocumento(id uint) error {
|
||||
@@ -206,6 +239,13 @@ func EmbeddingFromJSON(s string) ([]float32, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// BorrarChunksDeDocumento limpia los fragmentos de una fuente antes de volver a
|
||||
// procesarla. Sin esto, reprocesar deja la versión vieja y la nueva compitiendo
|
||||
// en la búsqueda, y la vieja puede ganar.
|
||||
func BorrarChunksDeDocumento(documentoID uint) error {
|
||||
return app.Http.Database.DB.Where("documento_id = ?", documentoID).Delete(&UmindChunk{}).Error
|
||||
}
|
||||
|
||||
func CreateUmindChunks(chunks []UmindChunk) error {
|
||||
if len(chunks) == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user