2026-07-20 09:48:24 +08:00
package dataset
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
"sort"
"strings"
"time"
"ragflow/internal/common"
"ragflow/internal/dao"
redisengine "ragflow/internal/engine/redis"
enginetypes "ragflow/internal/engine/types"
"ragflow/internal/entity"
modelModule "ragflow/internal/entity/models"
"ragflow/internal/service"
"ragflow/internal/utility"
"github.com/cespare/xxhash/v2"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func checkType ( indexType string ) bool {
haveType := false
for _ , t := range validIndexTypes {
if indexType == t {
haveType = true
}
}
return haveType
}
2026-07-24 16:47:12 +08:00
func ( d * DatasetService ) newRaptorOrGraphRagTask ( ctx context . Context , sampleDoc * entity . Document , taskType string , taskDocID string , queueDocID string , docIDs [ ] string ) ( * entity . Task , map [ string ] interface { } , error ) {
2026-07-20 09:48:24 +08:00
if docIDs == nil || len ( docIDs ) == 0 {
docIDs = make ( [ ] string , 0 )
}
if ! checkIndexTaskType ( taskType ) {
return nil , nil , errors . New ( "type should be graphrag, raptor or mindmap" )
}
2026-07-24 16:47:12 +08:00
chunkingConfig , err := d . documentDAO . GetChunkingConfig ( ctx , dao . DB , sampleDoc . ID )
2026-07-20 09:48:24 +08:00
if err != nil {
return nil , nil , err
}
hasher := xxhash . New ( )
keys := make ( [ ] string , 0 , len ( chunkingConfig ) )
for key := range chunkingConfig {
keys = append ( keys , key )
}
sort . Strings ( keys )
for _ , key := range keys {
_ , _ = hasher . Write ( [ ] byte ( key ) )
_ , _ = hasher . Write ( [ ] byte { 0 } )
v , mErr := json . Marshal ( chunkingConfig [ key ] )
if mErr != nil {
return nil , nil , mErr
}
_ , _ = hasher . Write ( v )
_ , _ = hasher . Write ( [ ] byte { 0 } )
}
taskID := utility . GenerateUUID ( )
beginAt := time . Now ( ) . Truncate ( time . Second )
progressMsg := beginAt . Format ( "15:04:05" ) + " created task " + taskType
for _ , field := range [ ] interface { } { taskDocID , maximumTaskPageNumber , maximumTaskPageNumber , taskType } {
_ , _ = hasher . Write ( [ ] byte ( fmt . Sprint ( field ) ) )
}
digest := fmt . Sprintf ( "%016x" , hasher . Sum64 ( ) )
task := & entity . Task {
ID : taskID ,
DocID : taskDocID ,
FromPage : maximumTaskPageNumber ,
ToPage : maximumTaskPageNumber ,
TaskType : taskType ,
ProgressMsg : & progressMsg ,
BeginAt : & beginAt ,
Digest : & digest ,
}
queueMessage := map [ string ] interface { } {
"id" : taskID ,
"doc_id" : queueDocID ,
"from_page" : maximumTaskPageNumber ,
"to_page" : maximumTaskPageNumber ,
"task_type" : taskType ,
"progress_msg" : progressMsg ,
"begin_at" : beginAt . Format ( "2006-01-02 15:04:05" ) ,
"digest" : digest ,
"doc_ids" : docIDs ,
}
return task , queueMessage , nil
}
func createDatasetIndexTaskInTx ( tx * gorm . DB , task * entity . Task , queueDocID string ) ( * entity . Document , error ) {
if task == nil {
return nil , errors . New ( "task is required" )
}
if err := tx . Create ( task ) . Error ; err != nil {
return nil , err
}
if queueDocID == "" {
return nil , nil
}
var document entity . Document
err := tx . Select ( "id" , "progress_msg" , "process_begin_at" ) . Where ( "id = ?" , queueDocID ) . First ( & document ) . Error
if err != nil {
if errors . Is ( err , gorm . ErrRecordNotFound ) {
return nil , nil
}
return nil , err
}
beginAt := time . Now ( ) . Truncate ( time . Second )
if task . BeginAt != nil {
beginAt = * task . BeginAt
}
2026-07-24 16:47:12 +08:00
if err = tx . Model ( & entity . Document { } ) . Where ( "id = ?" , queueDocID ) . Updates ( map [ string ] interface { } {
2026-07-20 09:48:24 +08:00
"progress_msg" : "Task is queued..." ,
"process_begin_at" : beginAt ,
} ) . Error ; err != nil {
return nil , err
}
return & document , nil
}
2026-08-02 22:50:54 +08:00
func enqueueDatasetIndexTask ( ctx context . Context , priority int , queueMessage map [ string ] interface { } ) error {
2026-07-20 09:48:24 +08:00
redisClient := redisengine . Get ( )
2026-08-02 22:50:54 +08:00
if redisClient == nil || ! redisClient . QueueProduct ( ctx , datasetIndexQueueName ( priority ) , queueMessage ) {
2026-07-27 15:06:48 +08:00
return errors . New ( "can't access Redis. Please check the Redis' status" )
2026-07-20 09:48:24 +08:00
}
return nil
}
func cleanupFailedDatasetIndexTask ( taskID string , updatedDocument * entity . Document , kbID string , indexType string ) error {
return dao . DB . Transaction ( func ( tx * gorm . DB ) error {
if err := tx . Unscoped ( ) . Where ( "id = ?" , taskID ) . Delete ( & entity . Task { } ) . Error ; err != nil {
return fmt . Errorf ( "delete task %s: %w" , taskID , err )
}
if column := datasetIndexTaskIDColumn ( indexType ) ; kbID != "" && column != "" {
if err := tx . Model ( & entity . Knowledgebase { } ) . Where ( "id = ? AND " + column + " = ?" , kbID , taskID ) . Update ( column , nil ) . Error ; err != nil {
return fmt . Errorf ( "clear dataset task id %s: %w" , taskID , err )
}
}
if updatedDocument == nil {
return nil
}
return tx . Model ( & entity . Document { } ) . Where ( "id = ?" , updatedDocument . ID ) . Updates ( map [ string ] interface { } {
"progress_msg" : updatedDocument . ProgressMsg ,
"process_begin_at" : updatedDocument . ProcessBeginAt ,
} ) . Error
} )
}
func datasetIndexTaskIDColumn ( indexType string ) string {
switch indexType {
case "graph" :
return "graphrag_task_id"
case "raptor" :
return "raptor_task_id"
case "mindmap" :
return "mindmap_task_id"
default :
return ""
}
}
func datasetIndexTaskFinishAtColumn ( indexType string ) string {
switch indexType {
case "graph" :
return "graphrag_task_finish_at"
case "raptor" :
return "raptor_task_finish_at"
case "mindmap" :
return "mindmap_task_finish_at"
default :
return ""
}
}
func checkIndexTaskType ( taskType string ) bool {
switch taskType {
case "graphrag" , "raptor" , "mindmap" :
return true
default :
return false
}
}
func datasetIndexTaskID ( kb * entity . Knowledgebase , indexType string ) string {
if kb == nil {
return ""
}
switch indexType {
case "graph" :
if kb . GraphragTaskID != nil {
return * kb . GraphragTaskID
}
case "raptor" :
if kb . RaptorTaskID != nil {
return * kb . RaptorTaskID
}
case "mindmap" :
if kb . MindmapTaskID != nil {
return * kb . MindmapTaskID
}
}
return ""
}
func datasetIndexTaskIDUpdate ( indexType , taskID string ) map [ string ] interface { } {
switch indexType {
case "graph" :
return map [ string ] interface { } { "graphrag_task_id" : taskID }
case "raptor" :
return map [ string ] interface { } { "raptor_task_id" : taskID }
case "mindmap" :
return map [ string ] interface { } { "mindmap_task_id" : taskID }
default :
return map [ string ] interface { } { }
}
}
func datasetIndexTaskIDs ( kb * entity . Knowledgebase ) [ ] string {
if kb == nil {
return nil
}
taskIDs := make ( [ ] string , 0 , 3 )
for _ , taskID := range [ ] * string { kb . GraphragTaskID , kb . RaptorTaskID , kb . MindmapTaskID } {
if taskID != nil && * taskID != "" {
taskIDs = append ( taskIDs , * taskID )
}
}
return common . Deduplicate ( taskIDs )
}
func datasetIndexQueueName ( priority int ) string {
return fmt . Sprintf ( "%s.%d.common" , serverQueueNamePrefix , priority )
}
2026-08-02 22:50:54 +08:00
func clearGraphPhaseMarkers ( ctx context . Context , redisClient * redisengine . Client , datasetID string ) {
2026-07-20 09:48:24 +08:00
if redisClient == nil || datasetID == "" {
return
}
for _ , phase := range [ ] string { graphPhaseResolutionDone , graphPhaseCommunityDone } {
2026-08-02 22:50:54 +08:00
if ! redisClient . Delete ( ctx , fmt . Sprintf ( "graphrag:phase:%s:%s" , datasetID , phase ) ) {
2026-07-20 09:48:24 +08:00
common . Warn ( "Failed to clear GraphRAG phase marker" , zap . String ( "dataset_id" , datasetID ) , zap . String ( "phase" , phase ) )
}
}
}
2026-07-24 16:47:12 +08:00
func ( d * DatasetService ) RunIndex ( ctx context . Context , userID , datasetID , indexType string ) ( map [ string ] interface { } , common . ErrorCode , error ) {
2026-07-20 09:48:24 +08:00
if ! checkType ( indexType ) {
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , fmt . Errorf ( "invalid index type '%s'. Must be one of %v" , indexType , validIndexTypes )
2026-07-20 09:48:24 +08:00
}
if datasetID == "" {
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , errors . New ( ` lack of "Dataset ID" ` )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
if ! d . kbDAO . Accessible ( ctx , dao . DB , datasetID , userID ) {
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , errors . New ( "no authorization" )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
kb , err := d . kbDAO . GetByID ( ctx , dao . DB , datasetID )
2026-07-20 09:48:24 +08:00
if err != nil {
if dao . IsNotFoundErr ( err ) {
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , errors . New ( "invalid Dataset ID" )
2026-07-20 09:48:24 +08:00
}
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
taskType := indexTypeToTaskType [ indexType ]
displayName := indexTypeToDisplayName [ indexType ]
2026-07-24 16:47:12 +08:00
documents , code , err := d . getDocumentsByDatasetForIndex ( ctx , datasetID )
2026-07-20 09:48:24 +08:00
if err != nil {
return nil , code , err
}
_ = documents
sampleDocument := documents [ 0 ]
documentIDs := make ( [ ] string , len ( documents ) )
for i , doc := range documents {
documentIDs [ i ] = doc . ID
}
2026-07-24 16:47:12 +08:00
task , queueMessage , err := d . newRaptorOrGraphRagTask ( ctx , sampleDocument , taskType , sampleDocument . ID , graphRaptorQueueDocID , documentIDs )
2026-07-20 09:48:24 +08:00
if err != nil {
common . Warn ( "Failed to build dataset index task" , zap . String ( "dataset_id" , datasetID ) , zap . String ( "task_type" , taskType ) , zap . Error ( err ) )
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
var updatedDocument * entity . Document
var dataErr error
2026-07-27 10:20:16 +08:00
err = dao . DB . WithContext ( ctx ) . Transaction ( func ( tx * gorm . DB ) error {
2026-07-20 09:48:24 +08:00
var lockedKB entity . Knowledgebase
2026-07-24 16:47:12 +08:00
if err = tx . Clauses ( clause . Locking { Strength : "UPDATE" } ) .
2026-07-20 09:48:24 +08:00
Where ( "id = ? AND status = ?" , kb . ID , string ( entity . StatusValid ) ) .
First ( & lockedKB ) . Error ; err != nil {
return err
}
existingTaskID := datasetIndexTaskID ( & lockedKB , indexType )
if existingTaskID != "" {
var existingTask entity . Task
taskErr := tx . Where ( "id = ?" , existingTaskID ) . First ( & existingTask ) . Error
if taskErr != nil {
if errors . Is ( taskErr , gorm . ErrRecordNotFound ) {
} else {
return taskErr
}
} else if existingTask . Progress != 1 && existingTask . Progress != - 1 {
2026-07-24 16:47:12 +08:00
dataErr = fmt . Errorf ( "task %s in progress with status %v. A %s Task is already running" , existingTaskID , existingTask . Progress , displayName )
2026-07-20 09:48:24 +08:00
return dataErr
}
}
updatedDocument , err = createDatasetIndexTaskInTx ( tx , task , graphRaptorQueueDocID )
if err != nil {
return err
}
return tx . Model ( & entity . Knowledgebase { } ) . Where ( "id = ?" , lockedKB . ID ) . Updates ( datasetIndexTaskIDUpdate ( indexType , task . ID ) ) . Error
} )
if err != nil {
if dataErr != nil {
return nil , common . CodeDataError , dataErr
}
common . Warn ( "Failed to create dataset index task" , zap . String ( "dataset_id" , datasetID ) , zap . String ( "task_type" , taskType ) , zap . Error ( err ) )
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
2026-08-02 22:50:54 +08:00
if err = enqueueDatasetIndexTask ( ctx , 0 , queueMessage ) ; err != nil {
2026-07-20 09:48:24 +08:00
if cleanupErr := cleanupFailedDatasetIndexTask ( task . ID , updatedDocument , kb . ID , indexType ) ; cleanupErr != nil {
err = errors . Join ( err , cleanupErr )
}
common . Warn ( "Failed to queue dataset index task" , zap . String ( "dataset_id" , datasetID ) , zap . String ( "task_type" , taskType ) , zap . Error ( err ) )
2026-07-24 16:47:12 +08:00
return nil , common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
return map [ string ] interface { } { "task_id" : task . ID } , common . CodeSuccess , nil
}
2026-07-24 16:47:12 +08:00
func ( d * DatasetService ) getDocumentsByDatasetForIndex ( ctx context . Context , datasetID string ) ( [ ] * entity . Document , common . ErrorCode , error ) {
documents , _ , err := d . documentDAO . GetByKBID ( ctx , dao . DB , datasetID )
2026-07-20 09:48:24 +08:00
if err != nil {
common . Warn ( "Failed to load dataset documents for index" , zap . String ( "dataset_id" , datasetID ) , zap . Error ( err ) )
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
if len ( documents ) == 0 {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , fmt . Errorf ( "no documents in Dataset %s" , datasetID )
2026-07-20 09:48:24 +08:00
}
return documents , common . CodeSuccess , nil
}
2026-07-27 10:20:16 +08:00
func ( d * DatasetService ) TraceIndex ( ctx context . Context , datasetID , userID , indexType string ) ( * entity . Task , common . ErrorCode , error ) {
2026-07-20 09:48:24 +08:00
if ! checkType ( indexType ) {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , fmt . Errorf ( "invalid index type '%s'. Must be one of %v" , indexType , validIndexTypes )
2026-07-20 09:48:24 +08:00
}
if datasetID == "" {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( ` lack of "Dataset ID" ` )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
if ! d . kbDAO . Accessible ( ctx , dao . DB , datasetID , userID ) {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( "no authorization" )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
kb , err := d . kbDAO . GetByID ( ctx , dao . DB , datasetID )
2026-07-20 09:48:24 +08:00
if err != nil {
if dao . IsNotFoundErr ( err ) {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( "invalid Dataset ID" )
2026-07-20 09:48:24 +08:00
}
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
taskID := datasetIndexTaskID ( kb , indexType )
var task * entity . Task
if taskID != "" {
2026-07-27 17:48:44 +08:00
task , err = d . taskDAO . GetByID ( ctx , dao . DB , taskID )
2026-07-20 09:48:24 +08:00
if err != nil {
if dao . IsNotFoundErr ( err ) {
return nil , common . CodeSuccess , nil
}
2026-07-27 15:06:48 +08:00
return nil , common . CodeServerError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
if task == nil {
return nil , common . CodeSuccess , nil
}
}
return task , common . CodeSuccess , nil
}
type embeddingCheckSample struct {
ChunkID string
KbID string
DocID string
DocName string
VectorField string
Vector [ ] float64
PageNum interface { }
Position interface { }
Top interface { }
ContentWithWeight string
QuestionKeywords [ ] string
}
2026-07-22 22:30:57 +08:00
func ( d * DatasetService ) CheckEmbedding ( ctx context . Context , userID , datasetID string , req * service . CheckEmbeddingRequest ) ( * service . EmbeddingCheckResponse , common . ErrorCode , error ) {
2026-07-20 09:48:24 +08:00
if datasetID == "" {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( ` lack of "Dataset ID" ` )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
if ! d . kbDAO . Accessible ( ctx , dao . DB , datasetID , userID ) {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( "no authorization" )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
kb , err := d . kbDAO . GetByID ( ctx , dao . DB , datasetID )
2026-07-20 09:48:24 +08:00
if err != nil {
if dao . IsNotFoundErr ( err ) {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( "invalid Dataset ID" )
2026-07-20 09:48:24 +08:00
}
2026-07-27 15:06:48 +08:00
return nil , common . CodeServerError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
if req == nil || strings . TrimSpace ( req . EmbeddingID ) == "" {
2026-07-27 15:06:48 +08:00
return nil , common . CodeDataError , errors . New ( "`embd_id` is required" )
2026-07-20 09:48:24 +08:00
}
embeddingID := strings . TrimSpace ( req . EmbeddingID )
2026-07-27 15:06:48 +08:00
if ok , message := d . verifyEmbeddingAvailability ( ctx , embeddingID , kb . TenantID ) ; ! ok {
2026-07-20 09:48:24 +08:00
return nil , common . CodeDataError , errors . New ( message )
}
if d . docEngine == nil {
return nil , common . CodeServerError , errors . New ( "doc engine not initialized" )
}
2026-07-27 15:06:48 +08:00
driver , modelName , apiConfig , maxTokens , err := service . NewModelProviderService ( ) . ResolveModelConfig ( ctx , kb . TenantID , entity . ModelTypeEmbedding , embeddingID )
2026-07-20 09:48:24 +08:00
if err != nil {
return nil , common . CodeDataError , err
}
embeddingModel := modelModule . NewEmbeddingModel ( driver , & modelName , apiConfig , maxTokens )
checkNum := defaultEmbeddingCheckNum
if req . CheckNum != nil {
checkNum = * req . CheckNum
}
if checkNum <= 0 {
checkNum = defaultEmbeddingCheckNum
}
2026-07-27 10:20:16 +08:00
samples , err := d . sampleRandomChunksWithVectors ( ctx , kb . TenantID , datasetID , checkNum )
2026-07-20 09:48:24 +08:00
if err != nil {
return nil , common . CodeServerError , err
}
if len ( samples ) == 0 {
return & service . EmbeddingCheckResponse {
Summary : datasetEmbeddingCheckSummary ( datasetID , embeddingID , 0 , nil , "" ) ,
Results : nil ,
} , common . CodeSuccess , nil
}
results := make ( [ ] service . EmbeddingCheckResult , 0 , len ( samples ) )
effectiveSimilarities := make ( [ ] float64 , 0 , len ( samples ) )
matchMode := "content_only"
for _ , sample := range samples {
if sample . Vector == nil || len ( sample . Vector ) == 0 {
continue
}
2026-07-27 10:20:16 +08:00
rawChunk , err := d . docEngine . GetChunk ( ctx , fmt . Sprintf ( "ragflow_%s" , kb . TenantID ) , sample . ChunkID , [ ] string { datasetID } )
2026-07-20 09:48:24 +08:00
if err != nil {
continue
}
chunkMap := datasetMap ( rawChunk )
if len ( chunkMap ) == 0 {
continue
}
title := datasetString ( chunkMap [ "title_tks" ] )
content := datasetString ( chunkMap [ "content_ltks" ] )
var titleVector [ ] [ ] float64
if title != "" {
2026-07-22 22:30:57 +08:00
titleVector , err = datasetEncodeEmbedding ( ctx , embeddingModel , [ ] string { title } )
2026-07-20 09:48:24 +08:00
if err != nil {
return nil , common . CodeServerError , err
}
}
var contentVector [ ] [ ] float64
if content != "" {
2026-07-22 22:30:57 +08:00
contentVector , err = datasetEncodeEmbedding ( ctx , embeddingModel , [ ] string { content } )
2026-07-20 09:48:24 +08:00
if err != nil {
return nil , common . CodeServerError , err
}
}
var vectors [ ] [ ] float64
if len ( titleVector ) > 0 && len ( contentVector ) > 0 {
vectors = [ ] [ ] float64 { titleVector [ 0 ] , contentVector [ 0 ] }
matchMode = "title_and_content"
} else if len ( titleVector ) > 0 {
vectors = titleVector
} else if len ( contentVector ) > 0 {
vectors = contentVector
} else {
continue
}
if len ( vectors [ 0 ] ) != len ( sample . Vector ) {
return nil , common . CodeDataError , fmt . Errorf ( "Embedding failure. The dimension (%d) of given embedding model is different from the original (%d)" , len ( vectors [ 0 ] ) , len ( sample . Vector ) )
}
var sim float64
if len ( vectors ) == 2 {
simContent := datasetCosSim ( vectors [ 1 ] , sample . Vector )
simMix := datasetCosSim ( datasetMixVectors ( vectors [ 0 ] , vectors [ 1 ] , 0.1 ) , sample . Vector )
sim = simContent
if simMix > sim {
sim = simMix
matchMode = "title+content"
}
} else {
sim = datasetCosSim ( vectors [ 0 ] , sample . Vector )
}
sim = datasetRoundFloat ( sim , 6 )
effectiveSimilarities = append ( effectiveSimilarities , sim )
results = append ( results , service . EmbeddingCheckResult {
ChunkID : sample . ChunkID ,
DocID : sample . DocID ,
DocName : sample . DocName ,
VectorField : sample . VectorField ,
VectorDim : len ( sample . Vector ) ,
CosSim : sim ,
} )
}
summary := datasetEmbeddingCheckSummary ( datasetID , embeddingID , len ( samples ) , effectiveSimilarities , matchMode )
response := & service . EmbeddingCheckResponse { Summary : summary , Results : results }
if len ( effectiveSimilarities ) == 0 {
return nil , common . CodeDataError , errors . New ( "No embedded chunks are available to compare." )
}
if summary . AvgCosSim >= 0.9 {
return response , common . CodeSuccess , nil
}
return response , common . CodeNotEffective , errors . New ( "Embedding model switch failed: the average similarity between old and new vectors is below 0.9, indicating incompatible vector spaces." )
}
func ( d * DatasetService ) sampleRandomChunksWithVectors ( ctx context . Context , tenantID , datasetID string , n int ) ( [ ] embeddingCheckSample , error ) {
indexName := fmt . Sprintf ( "ragflow_%s" , tenantID )
totalResult , err := d . docEngine . Search ( ctx , & enginetypes . SearchRequest {
IndexNames : [ ] string { indexName } ,
KbIDs : [ ] string { datasetID } ,
Offset : 0 ,
Limit : 1 ,
Filter : map [ string ] interface { } {
"kb_id" : datasetID ,
"available_int" : 1 ,
} ,
} )
if err != nil {
return nil , err
}
if totalResult == nil || totalResult . Total <= 0 {
return [ ] embeddingCheckSample { } , nil
}
total := int ( totalResult . Total )
const maxEmbeddingSamples = 1024
if n < 0 {
return nil , fmt . Errorf ( "invalid sample size: %d" , n )
}
if n > maxEmbeddingSamples {
n = maxEmbeddingSamples
}
if n > total {
n = total
}
limit := total
if limit > 1000 {
limit = 1000
}
if n > limit {
n = limit
}
offsets := rand . Perm ( limit )
offsets = offsets [ : n ]
sort . Ints ( offsets )
baseFields := [ ] string { "docnm_kwd" , "doc_id" , "content_with_weight" , "page_num_int" , "position_int" , "top_int" }
samples := make ( [ ] embeddingCheckSample , 0 , n )
for _ , offset := range offsets {
searchResult , err := d . docEngine . Search ( ctx , & enginetypes . SearchRequest {
IndexNames : [ ] string { indexName } ,
KbIDs : [ ] string { datasetID } ,
Offset : offset ,
Limit : 1 ,
SelectFields : baseFields ,
Filter : map [ string ] interface { } {
"kb_id" : datasetID ,
"available_int" : 1 ,
} ,
} )
if err != nil {
return nil , err
}
if searchResult == nil || len ( searchResult . Chunks ) == 0 {
continue
}
chunkID := datasetChunkID ( searchResult . Chunks [ 0 ] )
if chunkID == "" {
continue
}
fullChunk , err := d . docEngine . GetChunk ( ctx , indexName , chunkID , [ ] string { datasetID } )
if err != nil {
return nil , err
}
chunkMap := datasetMap ( fullChunk )
if len ( chunkMap ) == 0 {
continue
}
vectorField := datasetGuessVecField ( chunkMap )
vector := datasetAsFloatVec ( chunkMap [ vectorField ] )
samples = append ( samples , embeddingCheckSample {
ChunkID : chunkID ,
KbID : datasetID ,
DocID : datasetString ( chunkMap [ "doc_id" ] ) ,
DocName : datasetString ( chunkMap [ "docnm_kwd" ] ) ,
VectorField : vectorField ,
Vector : vector ,
PageNum : chunkMap [ "page_num_int" ] ,
Position : chunkMap [ "position_int" ] ,
Top : chunkMap [ "top_int" ] ,
ContentWithWeight : datasetString ( chunkMap [ "content_with_weight" ] ) ,
QuestionKeywords : datasetStringSlice ( chunkMap [ "question_keywords" ] ) ,
} )
}
if len ( samples ) == 0 {
return nil , errors . New ( "no valid chunks with vectors found" )
}
return samples , nil
}
2026-07-27 15:06:48 +08:00
func ( d * DatasetService ) verifyEmbeddingAvailability ( ctx context . Context , embdID string , tenantID string ) ( bool , string ) {
_ , _ , _ , _ , err := service . NewModelProviderService ( ) . ResolveModelConfig ( ctx , tenantID , entity . ModelTypeEmbedding , embdID )
2026-07-20 09:48:24 +08:00
if err != nil {
return false , err . Error ( )
}
return true , ""
}
2026-07-27 10:20:16 +08:00
func ( d * DatasetService ) DeleteIndex ( ctx context . Context , userID , datasetID , indexType string , wipe bool ) ( common . ErrorCode , error ) {
2026-07-20 09:48:24 +08:00
if ! checkType ( indexType ) {
2026-07-27 15:06:48 +08:00
return common . CodeArgumentError , fmt . Errorf ( "invalid index type '%s'" , indexType )
2026-07-20 09:48:24 +08:00
}
if datasetID == "" {
2026-07-27 15:06:48 +08:00
return common . CodeDataError , errors . New ( ` lack of "Dataset ID" ` )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
if ! d . kbDAO . Accessible ( ctx , dao . DB , datasetID , userID ) {
2026-07-27 15:06:48 +08:00
return common . CodeDataError , errors . New ( "no authorization" )
2026-07-20 09:48:24 +08:00
}
2026-07-27 10:20:16 +08:00
kb , err := d . kbDAO . GetByID ( ctx , dao . DB , datasetID )
2026-07-20 09:48:24 +08:00
if err != nil {
if dao . IsNotFoundErr ( err ) {
2026-07-27 15:06:48 +08:00
return common . CodeDataError , errors . New ( "invalid Dataset ID" )
2026-07-20 09:48:24 +08:00
}
2026-07-27 15:06:48 +08:00
return common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
taskFinishAtField := datasetIndexTaskFinishAtColumn ( indexType )
taskID := datasetIndexTaskID ( kb , indexType )
common . Info ( "delete_index" , zap . String ( "dataset_id" , datasetID ) , zap . String ( "index_type" , indexType ) , zap . Bool ( "wipe" , wipe ) )
if taskID != "" {
redisClient := redisengine . Get ( )
2026-08-03 15:44:59 +08:00
if redisClient == nil || ! redisClient . Set ( ctx , fmt . Sprintf ( "%s-cancel" , taskID ) , "x" , time . Hour ) {
2026-07-20 09:48:24 +08:00
common . Warn ( "Failed to set dataset index cancellation marker" , zap . String ( "dataset_id" , datasetID ) , zap . String ( "task_id" , taskID ) )
}
if err := dao . DB . Unscoped ( ) . Where ( "id = ?" , taskID ) . Delete ( & entity . Task { } ) . Error ; err != nil {
common . Warn ( "Failed to delete dataset index task" , zap . String ( "dataset_id" , datasetID ) , zap . String ( "task_id" , taskID ) , zap . Error ( err ) )
2026-07-27 15:06:48 +08:00
return common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
}
if wipe && indexType == "graph" {
if d . docEngine == nil {
2026-07-27 15:06:48 +08:00
return common . CodeServerError , errors . New ( "document engine is not initialized" )
2026-07-20 09:48:24 +08:00
}
indexName := fmt . Sprintf ( "ragflow_%s" , kb . TenantID )
2026-07-27 10:20:16 +08:00
_ , err = d . docEngine . DeleteChunks ( ctx , map [ string ] interface { } {
2026-07-20 09:48:24 +08:00
"knowledge_graph_kwd" : [ ] interface { } { "graph" , "subgraph" , "entity" , "relation" , "community_report" } ,
"kb_id" : datasetID ,
} , indexName , datasetID )
if err != nil {
common . Warn ( "Failed to delete GraphRAG artefacts" , zap . String ( "dataset_id" , datasetID ) , zap . Error ( err ) )
2026-07-27 15:06:48 +08:00
return common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
2026-08-02 22:50:54 +08:00
clearGraphPhaseMarkers ( ctx , redisengine . Get ( ) , datasetID )
2026-07-20 09:48:24 +08:00
common . Info ( "delete_index: cleared GraphRAG artefacts and phase markers" , zap . String ( "dataset_id" , datasetID ) )
} else if wipe && indexType == "raptor" {
if d . docEngine == nil {
2026-07-27 15:06:48 +08:00
return common . CodeServerError , errors . New ( "document engine is not initialized" )
2026-07-20 09:48:24 +08:00
}
indexName := fmt . Sprintf ( "ragflow_%s" , kb . TenantID )
2026-07-27 10:20:16 +08:00
_ , err = d . docEngine . DeleteChunks ( ctx , map [ string ] interface { } {
2026-07-20 09:48:24 +08:00
"raptor_kwd" : [ ] interface { } { "raptor" } ,
"kb_id" : datasetID ,
} , indexName , datasetID )
if err != nil {
common . Warn ( "Failed to delete RAPTOR artefacts" , zap . String ( "dataset_id" , datasetID ) , zap . Error ( err ) )
2026-07-27 15:06:48 +08:00
return common . CodeDataError , errors . New ( "internal server error" )
2026-07-20 09:48:24 +08:00
}
}
updates := datasetIndexTaskIDUpdate ( indexType , "" )
if taskFinishAtField != "" {
updates [ taskFinishAtField ] = nil
}
if len ( updates ) > 0 {
2026-07-27 10:20:16 +08:00
if err = d . kbDAO . UpdateByID ( ctx , dao . DB , kb . ID , updates ) ; err != nil {
2026-07-20 09:48:24 +08:00
common . Warn ( "Failed to clear KB index task refs" , zap . String ( "dataset_id" , datasetID ) , zap . Error ( err ) )
}
}
return common . CodeSuccess , nil
}