Implement UpdateDataset and UpdateMetadata in GO (#13928)

### What problem does this PR solve?

Implement UpdateDataset and UpdateMetadata in GO

Add cli:
UPDATE CHUNK <chunk_id> OF DATASET <dataset_name> SET <update_fields>
REMOVE TAGS 'tag1', 'tag2' from DATASET 'dataset_name';
SET METADATA OF DOCUMENT <doc_id> TO <meta>


### Type of change

- [ ] Refactoring
This commit is contained in:
qinling0210
2026-04-07 09:44:51 +08:00
committed by GitHub
parent 60ec5880e5
commit 49386bc1b5
27 changed files with 1620 additions and 171 deletions

View File

@@ -17,6 +17,7 @@
package handler
import (
"encoding/json"
"net/http"
"ragflow/internal/common"
@@ -246,3 +247,122 @@ func (h *ChunkHandler) List(c *gin.Context) {
"message": "success",
})
}
// UpdateChunk updates a chunk
// @Summary Update Chunk
// @Description Update chunk fields
// @Tags chunks
// @Accept json
// @Produce json
// @Param dataset_id path string true "Dataset ID"
// @Param document_id path string true "Document ID"
// @Param chunk_id path string true "Chunk ID"
// @Param request body service.UpdateChunkRequest true "update chunk"
// @Success 200 {object} map[string]interface{}
// @Router /v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id} [put]
func (h *ChunkHandler) UpdateChunk(c *gin.Context) {
user, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}
// Get path parameters
datasetID := c.Param("dataset_id")
documentID := c.Param("document_id")
chunkID := c.Param("chunk_id")
if datasetID == "" || documentID == "" || chunkID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "dataset_id, document_id, and chunk_id are required",
})
return
}
// Validate allowed update fields
var rawBody map[string]interface{}
if err := json.NewDecoder(c.Request.Body).Decode(&rawBody); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "invalid JSON body: " + err.Error(),
})
return
}
// Allowed fields for update
allowedFields := map[string]bool{
"content": true,
"important_keywords": true,
"questions": true,
"available": true,
"positions": true,
"tag_kwd": true,
"tag_feas": true,
}
for field := range rawBody {
if !allowedFields[field] {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "Update field '" + field + "' is not supported. Updatable fields: content, important_keywords, questions, available, positions, tag_kwd, tag_feas",
})
return
}
}
// Build UpdateChunkRequest from rawBody
var req service.UpdateChunkRequest
if content, ok := rawBody["content"].(string); ok {
req.Content = &content
}
if importantKwd, ok := rawBody["important_keywords"].([]interface{}); ok {
req.ImportantKwd = make([]string, len(importantKwd))
for i, v := range importantKwd {
if s, ok := v.(string); ok {
req.ImportantKwd[i] = s
}
}
}
if questions, ok := rawBody["questions"].([]interface{}); ok {
req.Questions = make([]string, len(questions))
for i, v := range questions {
if s, ok := v.(string); ok {
req.Questions[i] = s
}
}
}
if available, ok := rawBody["available"].(bool); ok {
req.Available = &available
}
if positions, ok := rawBody["positions"].([]interface{}); ok {
req.Positions = positions
}
if tagKwd, ok := rawBody["tag_kwd"].([]interface{}); ok {
req.TagKwd = make([]string, len(tagKwd))
for i, v := range tagKwd {
if s, ok := v.(string); ok {
req.TagKwd[i] = s
}
}
}
req.TagFeas = rawBody["tag_feas"]
// Set path parameters
req.DatasetID = datasetID
req.DocumentID = documentID
req.ChunkID = chunkID
err := h.chunkService.UpdateChunk(&req, user.ID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "chunk updated successfully",
})
}

View File

@@ -17,6 +17,8 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
"ragflow/internal/common"
"strconv"
@@ -372,3 +374,103 @@ func (h *DocumentHandler) MetadataSummary(c *gin.Context) {
},
})
}
// SetMetaRequest represents the request for setting document metadata
type SetMetaRequest struct {
DocID string `json:"doc_id" binding:"required"`
Meta string `json:"meta" binding:"required"`
}
// SetMeta handles the set metadata request for a document
// @Summary Set Document Metadata
// @Description Set metadata for a specific document
// @Tags documents
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param request body SetMetaRequest true "metadata info"
// @Success 200 {object} map[string]interface{}
// @Router /v1/document/set_meta [post]
func (h *DocumentHandler) SetMeta(c *gin.Context) {
_, errorCode, errorMessage := GetUser(c)
if errorCode != common.CodeSuccess {
jsonError(c, errorCode, errorMessage)
return
}
var req SetMetaRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"message": err.Error(),
})
return
}
if req.DocID == "" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"message": "doc_id is required",
})
return
}
// Parse meta JSON string
var meta map[string]interface{}
if err := json.Unmarshal([]byte(req.Meta), &meta); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"message": "Json syntax error: " + err.Error(),
})
return
}
if meta == nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"message": "meta is required",
})
return
}
// Validate meta values - must be str, int, float, or list of those
for k, v := range meta {
switch val := v.(type) {
case string, int, float64:
// Valid
case []interface{}:
for _, item := range val {
if _, ok := item.(string); !ok {
if _, ok := item.(float64); !ok {
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"message": fmt.Sprintf("Unsupported type in list for key %s: %T", k, item),
})
return
}
}
}
default:
c.JSON(http.StatusBadRequest, gin.H{
"code": 1,
"message": fmt.Sprintf("Unsupported type for key %s: %T", k, v),
})
return
}
}
err := h.documentService.SetDocumentMetadata(req.DocID, meta)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 1,
"message": "Failed to set metadata: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": true,
})
}

View File

@@ -444,6 +444,34 @@ func (h *KnowledgebaseHandler) RemoveTags(c *gin.Context) {
return
}
// Get KB to find tenant_id and build index name
kb, err := h.kbService.GetByID(kbID)
if err != nil {
jsonError(c, common.CodeDataError, "knowledge base not found")
return
}
// Build index name prefix: ragflow_<tenant_id>
indexName := "ragflow_" + kb.TenantID
// For each tag, call UpdateChunk to remove it from documents
for _, tag := range req.Tags {
condition := map[string]interface{}{
"tag_kwd": tag,
"kb_id": kbID,
}
newValue := map[string]interface{}{
"remove": map[string]interface{}{
"tag_kwd": tag,
},
}
err := h.kbService.RemoveTag(condition, newValue, indexName, kbID)
if err != nil {
jsonError(c, common.CodeServerError, "Failed to remove tag: "+err.Error())
return
}
}
jsonResponse(c, common.CodeSuccess, true, "success")
}