From 57625c919aaadd1a7231a349d5eb8f0cb479084a Mon Sep 17 00:00:00 2001 From: Wang Qi Date: Mon, 6 Jul 2026 16:06:27 +0800 Subject: [PATCH] Fix Tag weight should be greater than 0 (#16657) --- common/tag_feature_utils.py | 6 +- internal/handler/chunk.go | 13 +++++ internal/handler/chunk_test.go | 32 ++++++++++ internal/service/chunk/chunk.go | 44 ++++++++++++-- internal/service/chunk_types.go | 58 ++++++++++++++++++- .../chunk-creating-modal/tag-feature-item.tsx | 4 +- .../chunk-creating-modal/tag-feature-item.tsx | 4 +- 7 files changed, 147 insertions(+), 14 deletions(-) diff --git a/common/tag_feature_utils.py b/common/tag_feature_utils.py index 27488c2d5e..45463cfbb0 100644 --- a/common/tag_feature_utils.py +++ b/common/tag_feature_utils.py @@ -76,10 +76,10 @@ def validate_tag_features(raw): if not key: raise ValueError("keys must be non-empty strings") if isinstance(value, bool) or not isinstance(value, (int, float)): - raise ValueError("values must be finite numbers") + raise ValueError("values must be finite numbers greater than 0") numeric = float(value) - if not math.isfinite(numeric): - raise ValueError("values must be finite numbers") + if not math.isfinite(numeric) or numeric <= 0: + raise ValueError("values must be finite numbers greater than 0") cleaned[key] = numeric return cleaned diff --git a/internal/handler/chunk.go b/internal/handler/chunk.go index 0119934700..6d54a296cb 100644 --- a/internal/handler/chunk.go +++ b/internal/handler/chunk.go @@ -719,6 +719,19 @@ func (h *ChunkHandler) UpdateChunk(c *gin.Context) { err := h.chunkService.UpdateChunk(&req, user.ID) if err != nil { + var coded interface { + Code() common.ErrorCode + } + if errors.As(err, &coded) { + switch coded.Code() { + case common.CodeArgumentError, common.CodeBadRequest, common.CodeDataError: + c.JSON(http.StatusBadRequest, gin.H{ + "code": coded.Code(), + "message": err.Error(), + }) + return + } + } c.JSON(http.StatusInternalServerError, gin.H{ "code": 500, "message": err.Error(), diff --git a/internal/handler/chunk_test.go b/internal/handler/chunk_test.go index 77ec780ecb..828a1abe3a 100644 --- a/internal/handler/chunk_test.go +++ b/internal/handler/chunk_test.go @@ -27,6 +27,19 @@ type mockChunkSvc struct { stopParsingFn func(userID, datasetID string, req service.StopParsingRequest) (*service.StopParsingResponse, common.ErrorCode, error) } +type codedTestError struct { + code common.ErrorCode + msg string +} + +func (e codedTestError) Error() string { + return e.msg +} + +func (e codedTestError) Code() common.ErrorCode { + return e.code +} + func (m *mockChunkSvc) RetrievalTest(req *service.RetrievalTestRequest, userID string) (*service.RetrievalTestResponse, error) { if m.retrievalTestFn != nil { return m.retrievalTestFn(req, userID) @@ -276,6 +289,25 @@ func TestChunkHandlerUpdateChunkUsesPathIDs(t *testing.T) { } } +func TestChunkHandlerUpdateChunkValidationErrorIsBadRequest(t *testing.T) { + mock := &mockChunkSvc{} + r, h := setupChunkHandlerWithUser("user-1", mock) + r.PATCH("/api/v1/datasets/:dataset_id/documents/:document_id/chunks/:chunk_id", h.UpdateChunk) + + mock.updateChunkFn = func(req *service.UpdateChunkRequest, userID string) error { + return codedTestError{code: common.CodeArgumentError, msg: "`tag_feas` values must be finite numbers greater than 0"} + } + + req := httptest.NewRequest(http.MethodPatch, "/api/v1/datasets/kb-1/documents/doc-1/chunks/chunk-1", strings.NewReader(`{"tag_feas":{"tag":0}}`)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + if w.Code != http.StatusBadRequest { + t.Fatalf("status = %d, body = %s", w.Code, w.Body.String()) + } +} + func TestChunkRetrieval_EmptyQuestion(t *testing.T) { r, _ := setupChunkRetrievalTest("user1") diff --git a/internal/service/chunk/chunk.go b/internal/service/chunk/chunk.go index 83b2f74f2b..bffaa865f4 100644 --- a/internal/service/chunk/chunk.go +++ b/internal/service/chunk/chunk.go @@ -1682,7 +1682,11 @@ func (s *ChunkService) UpdateChunk(req *service.UpdateChunkRequest, userID strin // Tag features if req.TagFeas != nil { - d["tag_feas"] = req.TagFeas + tagFeas, err := validateTagFeatures(req.TagFeas) + if err != nil { + return updateChunkError{code: common.CodeArgumentError, message: "`tag_feas` " + err.Error()} + } + d["tag_feas"] = tagFeas } // Always include id @@ -1932,6 +1936,19 @@ type addChunkError struct { message string } +type updateChunkError struct { + code common.ErrorCode + message string +} + +func (e updateChunkError) Error() string { + return e.message +} + +func (e updateChunkError) Code() common.ErrorCode { + return e.code +} + func (e addChunkError) Error() string { return e.message } @@ -1953,27 +1970,42 @@ func validateTagFeatures(raw interface{}) (map[string]float64, error) { } switch typed := value.(type) { case float64: - if math.IsNaN(typed) || math.IsInf(typed, 0) { - return nil, fmt.Errorf("values must be finite numbers") + if math.IsNaN(typed) || math.IsInf(typed, 0) || typed <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") } cleaned[key] = typed case float32: - if math.IsNaN(float64(typed)) || math.IsInf(float64(typed), 0) { - return nil, fmt.Errorf("values must be finite numbers") + if math.IsNaN(float64(typed)) || math.IsInf(float64(typed), 0) || typed <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") } cleaned[key] = float64(typed) case int: + if typed <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") + } cleaned[key] = float64(typed) case int8: + if typed <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") + } cleaned[key] = float64(typed) case int16: + if typed <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") + } cleaned[key] = float64(typed) case int32: + if typed <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") + } cleaned[key] = float64(typed) case int64: + if typed <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") + } cleaned[key] = float64(typed) default: - return nil, fmt.Errorf("values must be finite numbers") + return nil, fmt.Errorf("values must be finite numbers greater than 0") } } return cleaned, nil diff --git a/internal/service/chunk_types.go b/internal/service/chunk_types.go index 7e2dbc2cf8..21fdf9592e 100644 --- a/internal/service/chunk_types.go +++ b/internal/service/chunk_types.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "math" "ragflow/internal/common" "ragflow/internal/engine/redis" "ragflow/internal/entity" @@ -636,7 +637,11 @@ func (s *ChunkService) UpdateChunk(req *UpdateChunkRequest, userID string) error // Tag features if req.TagFeas != nil { - d["tag_feas"] = req.TagFeas + tagFeas, err := validateUpdateTagFeatures(req.TagFeas) + if err != nil { + return updateChunkError{code: common.CodeArgumentError, message: "`tag_feas` " + err.Error()} + } + d["tag_feas"] = tagFeas } // Always include id @@ -655,6 +660,57 @@ func (s *ChunkService) UpdateChunk(req *UpdateChunkRequest, userID string) error return nil } +type updateChunkError struct { + code common.ErrorCode + message string +} + +func (e updateChunkError) Error() string { + return e.message +} + +func (e updateChunkError) Code() common.ErrorCode { + return e.code +} + +func validateUpdateTagFeatures(raw interface{}) (map[string]float64, error) { + parsed, ok := raw.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("must be an object mapping string tags to finite numeric scores greater than 0") + } + cleaned := make(map[string]float64, len(parsed)) + for key, value := range parsed { + key = strings.TrimSpace(key) + if key == "" { + return nil, fmt.Errorf("keys must be non-empty strings") + } + var numeric float64 + switch typed := value.(type) { + case float64: + numeric = typed + case float32: + numeric = float64(typed) + case int: + numeric = float64(typed) + case int8: + numeric = float64(typed) + case int16: + numeric = float64(typed) + case int32: + numeric = float64(typed) + case int64: + numeric = float64(typed) + default: + return nil, fmt.Errorf("values must be finite numbers greater than 0") + } + if math.IsNaN(numeric) || math.IsInf(numeric, 0) || numeric <= 0 { + return nil, fmt.Errorf("values must be finite numbers greater than 0") + } + cleaned[key] = numeric + } + return cleaned, nil +} + // RemoveChunksRequest request for removing chunks type RemoveChunksRequest struct { DocID string `json:"doc_id"` diff --git a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/tag-feature-item.tsx b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/tag-feature-item.tsx index 77219ce7cd..645581a77d 100644 --- a/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/tag-feature-item.tsx +++ b/web/src/pages/chunk/parsed-result/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/tag-feature-item.tsx @@ -107,7 +107,7 @@ export const TagFeatureItem = () => { 'knowledgeConfiguration.frequency', )} max={10} - min={0} + min={1} /> @@ -125,7 +125,7 @@ export const TagFeatureItem = () => {