From 74218bdd6a565c8360b55922903a34c6d4feff17 Mon Sep 17 00:00:00 2001 From: Haruko386 Date: Fri, 24 Jul 2026 11:01:30 +0800 Subject: [PATCH] fix: search chunk cannot get result (#17328) --- internal/service/chunk/chunk.go | 39 +++++++++++- internal/service/chunk/chunk_test.go | 94 ++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/internal/service/chunk/chunk.go b/internal/service/chunk/chunk.go index 5f02016a31..06124c8349 100644 --- a/internal/service/chunk/chunk.go +++ b/internal/service/chunk/chunk.go @@ -839,15 +839,44 @@ func (s *ChunkService) List(req *service.ListChunksRequest, userID string) (*ser page := common.CoalesceInt(req.Page, 1) size := common.CoalesceInt(req.Size, 30) - keywords := req.Keywords + keywords := strings.TrimSpace(req.Keywords) + matchExprs := make([]interface{}, 0, 1) + if keywords != "" { + matchExprs = append(matchExprs, &types.MatchTextExpr{ + MatchingText: keywords, + TopN: size, + }) + } // Build search request - same as retrieval test but filtered by doc_id searchReq := &types.SearchRequest{ IndexNames: []string{indexName}, - MatchExprs: []interface{}{keywords}, + MatchExprs: matchExprs, KbIDs: kbIDs, Offset: (page - 1) * size, Limit: size, + SelectFields: []string{ + "id", + "content_with_weight", + "img_id", + "position_int", + "docnm", + "important_keywords", + "questions", + "entities_kwd", + "entity_kwd", + "entity_type_kwd", + "from_entity_kwd", + "name_kwd", + "raptor_kwd", + "removed_kwd", + "source_id", + "tag_kwd", + "to_entity_kwd", + "toc_kwd", + "doc_type_kwd", + "available_int", + }, Filter: map[string]interface{}{ "doc_id": req.DocID, }, @@ -887,8 +916,12 @@ func (s *ChunkService) List(req *service.ListChunksRequest, userID string) (*ser result["positions"] = v case "id": result["chunk_id"] = v - case "content": + case "content_with_weight": result["content_with_weight"] = v + case "content": + if _, ok := result["content_with_weight"]; !ok { + result["content_with_weight"] = v + } case "docnm": result["docnm_kwd"] = v case "important_keywords": diff --git a/internal/service/chunk/chunk_test.go b/internal/service/chunk/chunk_test.go index a178fcfdda..c99dd90c7e 100644 --- a/internal/service/chunk/chunk_test.go +++ b/internal/service/chunk/chunk_test.go @@ -16,6 +16,7 @@ import ( "ragflow/internal/service/document" "ragflow/internal/storage" "reflect" + "slices" "strings" "testing" "time" @@ -278,6 +279,79 @@ func TestParseRejectsRunningDocument(t *testing.T) { } } +func TestListBuildsMatchTextExprForKeywords(t *testing.T) { + db := setupChunkTestDB(t) + pushChunkTestDB(t, db) + + userID := "user-1" + tenantID := "tenant-1" + datasetID := "kb-1" + documentID := "doc-1" + insertChunkTestUserTenant(t, userID, tenantID) + insertChunkTestKB(t, datasetID, tenantID) + insertChunkTestDoc(t, documentID, datasetID) + + engine := &listChunksSearchEngine{} + svc := &ChunkService{ + docEngine: engine, + kbDAO: dao.NewKnowledgebaseDAO(), + userTenantDAO: dao.NewUserTenantDAO(), + documentDAO: dao.NewDocumentDAO(), + } + + page := 2 + size := 5 + resp, err := svc.List(&service.ListChunksRequest{ + DatasetID: datasetID, + DocID: documentID, + Page: &page, + Size: &size, + Keywords: " invoice terms ", + }, userID) + if err != nil { + t.Fatalf("List() error = %v", err) + } + if resp.Total != 1 { + t.Fatalf("total = %d, want 1", resp.Total) + } + if engine.searchReq == nil { + t.Fatal("expected Search to be called") + } + if engine.searchReq.Offset != 5 || engine.searchReq.Limit != 5 { + t.Fatalf("pagination = offset %d limit %d, want offset 5 limit 5", engine.searchReq.Offset, engine.searchReq.Limit) + } + if !reflect.DeepEqual(engine.searchReq.KbIDs, []string{datasetID}) { + t.Fatalf("KbIDs = %#v, want %#v", engine.searchReq.KbIDs, []string{datasetID}) + } + if got := engine.searchReq.Filter["doc_id"]; got != documentID { + t.Fatalf("doc_id filter = %#v, want %q", got, documentID) + } + if slices.Contains(engine.searchReq.SelectFields, "content") { + t.Fatalf("SelectFields = %#v, should not request content with content_with_weight", engine.searchReq.SelectFields) + } + for _, field := range []string{"content_with_weight", "img_id", "position_int"} { + if !slices.Contains(engine.searchReq.SelectFields, field) { + t.Fatalf("SelectFields = %#v, missing %q", engine.searchReq.SelectFields, field) + } + } + if got := resp.Chunks[0]["content_with_weight"]; got != "weighted invoice terms body" { + t.Fatalf("formatted content_with_weight = %#v, want %q", got, "weighted invoice terms body") + } + if len(engine.searchReq.MatchExprs) != 1 { + t.Fatalf("MatchExprs length = %d, want 1", len(engine.searchReq.MatchExprs)) + } + matchText, ok := engine.searchReq.MatchExprs[0].(*types.MatchTextExpr) + if !ok { + t.Fatalf("MatchExprs[0] = %T, want *types.MatchTextExpr", engine.searchReq.MatchExprs[0]) + } + if matchText.MatchingText != "invoice terms" { + t.Fatalf("MatchingText = %q, want %q", matchText.MatchingText, "invoice terms") + } + if matchText.TopN != size { + t.Fatalf("TopN = %d, want %d", matchText.TopN, size) + } +} + func TestAddChunkSuccess(t *testing.T) { ctx := t.Context() db := setupChunkTestDB(t) @@ -1032,6 +1106,26 @@ func (e *addChunkTestEngine) InsertChunks(_ context.Context, chunks []map[string return nil, e.insertErr } +type listChunksSearchEngine struct { + parseTestDocEngine + searchReq *types.SearchRequest +} + +func (e *listChunksSearchEngine) Search(_ context.Context, req *types.SearchRequest) (*types.SearchResult, error) { + e.searchReq = req + return &types.SearchResult{ + Chunks: []map[string]interface{}{ + { + "id": "chunk-1", + "content": "plain invoice terms body", + "content_with_weight": "weighted invoice terms body", + "doc_id": "doc-1", + }, + }, + Total: 1, + }, nil +} + type chunkImageStorage struct { exists bool oldBinary []byte