mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-21 07:01:04 +08:00
feat(go-api) sessions message update (#16517)
### Summary ``` /api/v1/chats/<chat_id>/sessions/<session_id>/messages/<msg_id> DELETE /api/v1/chats/<chat_id>/sessions/<session_id>/messages/<msg_id>/feedback PUT ``` Migrates the chat session message delete and feedback APIs to the Go server, matching the Python behavior for authorization, session ownership checks, message/reference updates, and feedback validation.
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"ragflow/internal/common"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
infinity "github.com/infiniflow/infinity-go-sdk"
|
||||
"go.uber.org/zap"
|
||||
@@ -38,6 +40,13 @@ import (
|
||||
// ChinesePunctRegex splits on comma, semicolon, Chinese punctuations, and newlines
|
||||
var ChinesePunctRegex = regexp.MustCompile(`[,,;;、\r\n]+`)
|
||||
|
||||
const (
|
||||
pagerankAdjustRetryCount = 2
|
||||
pagerankAdjustLockCount = 256
|
||||
)
|
||||
|
||||
var pagerankAdjustLocks [pagerankAdjustLockCount]sync.Mutex
|
||||
|
||||
// CreateChunkStore creates a chunk table in Infinity
|
||||
// baseName is the table name prefix (e.g., "ragflow_<tenant_id>")
|
||||
// The full table name is built as "{baseName}_{datasetID}"
|
||||
@@ -518,6 +527,97 @@ func (e *infinityEngine) UpdateChunks(ctx context.Context, condition map[string]
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdjustChunkPagerank adjusts pagerank_fea and clamps it to [minWeight, maxWeight].
|
||||
func (e *infinityEngine) AdjustChunkPagerank(ctx context.Context, baseName, chunkID, datasetID string, delta, minWeight, maxWeight float64) error {
|
||||
if baseName == "" {
|
||||
return fmt.Errorf("index name cannot be empty")
|
||||
}
|
||||
if chunkID == "" {
|
||||
return fmt.Errorf("chunk id cannot be empty")
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if e.client == nil || e.client.conn == nil {
|
||||
return fmt.Errorf("Infinity client not initialized")
|
||||
}
|
||||
|
||||
tableName := buildChunkTableName(baseName, datasetID)
|
||||
lock := pagerankAdjustLock(tableName + ":" + chunkID)
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
db, err := e.client.conn.GetDatabase(e.client.dbName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database: %w", err)
|
||||
}
|
||||
table, err := db.GetTable(tableName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get table %s: %w", tableName, err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
filter := fmt.Sprintf("id = '%s'", escapeFilterValue(chunkID))
|
||||
for attempt := 0; attempt <= pagerankAdjustRetryCount; attempt++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := table.Output([]string{common.PAGERANK_FLD, "row_id()"}).Filter(filter).ToResult()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
qr, ok := result.(*infinity.QueryResult)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected query result type: %T", result)
|
||||
}
|
||||
|
||||
rowID, ok := firstQueryInt64(qr.Data, "ROW_ID", "row_id()", "row_id")
|
||||
if !ok {
|
||||
return fmt.Errorf("%w: %s", types.ErrDocumentNotFound, chunkID)
|
||||
}
|
||||
|
||||
currentWeight := 0.0
|
||||
if currentValue, ok := firstQueryValue(qr.Data, common.PAGERANK_FLD); ok {
|
||||
if weight, ok := coerceToFloat(currentValue); ok {
|
||||
currentWeight = weight
|
||||
}
|
||||
}
|
||||
nextWeight := currentWeight + delta
|
||||
if nextWeight < minWeight {
|
||||
nextWeight = minWeight
|
||||
}
|
||||
if nextWeight > maxWeight {
|
||||
nextWeight = maxWeight
|
||||
}
|
||||
if floatsEqual(currentWeight, nextWeight) {
|
||||
return nil
|
||||
}
|
||||
|
||||
updateFilter := fmt.Sprintf("_row_id = %d AND %s = %s", rowID, common.PAGERANK_FLD, formatFilterFloat(currentWeight))
|
||||
if _, err := table.Update(updateFilter, map[string]interface{}{common.PAGERANK_FLD: nextWeight}); err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
verifyResult, err := table.Output([]string{common.PAGERANK_FLD}).Filter(filter).ToResult()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
verifyQR, ok := verifyResult.(*infinity.QueryResult)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected query result type: %T", verifyResult)
|
||||
}
|
||||
if currentValue, ok := firstQueryValue(verifyQR.Data, common.PAGERANK_FLD); ok {
|
||||
if actualWeight, ok := coerceToFloat(currentValue); ok && floatsEqual(actualWeight, nextWeight) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
lastErr = fmt.Errorf("pagerank update conflict")
|
||||
}
|
||||
return fmt.Errorf("failed to adjust chunk pagerank: %w", lastErr)
|
||||
}
|
||||
|
||||
// DeleteChunks deletes chunks from a dataset table
|
||||
// Table name format: {baseName}_{datasetID}
|
||||
// condition specifies which chunks to delete
|
||||
@@ -1976,6 +2076,77 @@ func escapeFilterValue(s string) string {
|
||||
return strings.ReplaceAll(s, "'", "''")
|
||||
}
|
||||
|
||||
func firstQueryValue(data map[string][]interface{}, columns ...string) (interface{}, bool) {
|
||||
for _, column := range columns {
|
||||
values, ok := data[column]
|
||||
if ok && len(values) > 0 {
|
||||
return values[0], true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func firstQueryInt64(data map[string][]interface{}, columns ...string) (int64, bool) {
|
||||
value, ok := firstQueryValue(data, columns...)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return int64(v), true
|
||||
case int8:
|
||||
return int64(v), true
|
||||
case int16:
|
||||
return int64(v), true
|
||||
case int32:
|
||||
return int64(v), true
|
||||
case int64:
|
||||
return v, true
|
||||
case uint:
|
||||
if uint64(v) <= ^uint64(0)>>1 {
|
||||
return int64(v), true
|
||||
}
|
||||
case uint8:
|
||||
return int64(v), true
|
||||
case uint16:
|
||||
return int64(v), true
|
||||
case uint32:
|
||||
return int64(v), true
|
||||
case uint64:
|
||||
if v <= ^uint64(0)>>1 {
|
||||
return int64(v), true
|
||||
}
|
||||
case float32:
|
||||
return int64(v), true
|
||||
case float64:
|
||||
return int64(v), true
|
||||
case string:
|
||||
parsed, err := strconv.ParseInt(v, 10, 64)
|
||||
if err == nil {
|
||||
return parsed, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func pagerankAdjustLock(key string) *sync.Mutex {
|
||||
hash := fnv.New32a()
|
||||
_, _ = hash.Write([]byte(key))
|
||||
return &pagerankAdjustLocks[hash.Sum32()%pagerankAdjustLockCount]
|
||||
}
|
||||
|
||||
func formatFilterFloat(value float64) string {
|
||||
return strconv.FormatFloat(value, 'f', -1, 64)
|
||||
}
|
||||
|
||||
func floatsEqual(a, b float64) bool {
|
||||
diff := a - b
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
return diff < 1e-9
|
||||
}
|
||||
|
||||
// equivalentConditionToStr converts a condition map to an Infinity filter string
|
||||
func equivalentConditionToStr(condition map[string]interface{}) string {
|
||||
if len(condition) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user