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 = () => {