Refactor: vector_similarity_weight -> keywords_similarity_weight (#20385)

This commit is contained in:
Wang Qi
2026-09-29 19:03:34 +08:00
committed by GitHub
parent 8614e2c38d
commit f03ee77821
17 changed files with 174 additions and 87 deletions
+3 -7
View File
@@ -708,7 +708,7 @@ Success:
"token_num": 0,
"update_date": "2025-04-28T18:40:41",
"update_time": 1745836841611,
"vector_similarity_weight": 0.3,
"keywords_similarity_weight": 0.7,
},
}
```
@@ -1018,7 +1018,7 @@ Success:
"token_num": 12744,
"update_date": "Thu, 10 Oct 2024 04:07:23 GMT",
"update_time": 1728533243536,
"vector_similarity_weight": 0.3
"keywords_similarity_weight": 0.7
}
],
"total_datasets": 1
@@ -1059,7 +1059,7 @@ Success (with `include_parsing_status=true`):
"unstart_count": 0,
"update_date": "2026-03-09T18:59:32",
"update_time": 1773053972723,
"vector_similarity_weight": 0.3
"keywords_similarity_weight": 0.7
}
],
"total_datasets": 1
@@ -2764,7 +2764,6 @@ Success:
},
"rerank_id": "",
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"keywords_similarity_weight": 0.7,
"top_n": 6,
"prompt_type": "simple",
@@ -2903,7 +2902,6 @@ Success: returns the full updated chat assistant object.
"parameters": [{"key": "knowledge", "optional": false}]
},
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"keywords_similarity_weight": 0.7,
"top_n": 6,
"top_k": 1024,
@@ -2984,7 +2982,6 @@ Success:
},
"rerank_id": "",
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"keywords_similarity_weight": 0.7,
"top_n": 6,
"status": "1",
@@ -3267,7 +3264,6 @@ Success:
},
"rerank_id": "",
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
"keywords_similarity_weight": 0.7,
"top_n": 6,
"prompt_type": "simple",
+2 -1
View File
@@ -226,7 +226,8 @@ func (dao *KnowledgebaseDAO) GetByTenantIDs(ctx context.Context, db *gorm.DB, te
Select(`knowledgebase.id, knowledgebase.avatar, knowledgebase.name,
knowledgebase.language, knowledgebase.description, knowledgebase.tenant_id,
knowledgebase.permission, knowledgebase.doc_num, knowledgebase.token_num,
knowledgebase.chunk_num, knowledgebase.parser_id, knowledgebase.parser_config,
knowledgebase.chunk_num, knowledgebase.similarity_threshold,
knowledgebase.vector_similarity_weight, knowledgebase.parser_id, knowledgebase.parser_config,
knowledgebase.pagerank, knowledgebase.embd_id,
knowledgebase.tenant_embd_id,
user.nickname, user.avatar as tenant_avatar, knowledgebase.update_time`).
+35 -33
View File
@@ -150,21 +150,21 @@ func (kb *Knowledgebase) TableName() string {
// ToMap converts Knowledgebase to a map for JSON response
func (kb *Knowledgebase) ToMap() map[string]interface{} {
result := map[string]interface{}{
"id": kb.ID,
"tenant_id": kb.TenantID,
"name": kb.Name,
"embd_id": kb.EmbdID,
"permission": kb.Permission,
"created_by": kb.CreatedBy,
"doc_num": kb.DocNum,
"token_num": kb.TokenNum,
"chunk_num": kb.ChunkNum,
"similarity_threshold": kb.SimilarityThreshold,
"vector_similarity_weight": kb.VectorSimilarityWeight,
"parser_id": kb.ParserID,
"parser_config": kb.ParserConfig,
"pagerank": kb.Pagerank,
"create_time": kb.CreateTime,
"id": kb.ID,
"tenant_id": kb.TenantID,
"name": kb.Name,
"embd_id": kb.EmbdID,
"permission": kb.Permission,
"created_by": kb.CreatedBy,
"doc_num": kb.DocNum,
"token_num": kb.TokenNum,
"chunk_num": kb.ChunkNum,
"similarity_threshold": kb.SimilarityThreshold,
"keywords_similarity_weight": 1 - kb.VectorSimilarityWeight,
"parser_id": kb.ParserID,
"parser_config": kb.ParserConfig,
"pagerank": kb.Pagerank,
"create_time": kb.CreateTime,
}
if kb.Avatar != nil {
@@ -236,24 +236,26 @@ type KnowledgebaseDetail struct {
// KnowledgebaseListItem represents a knowledge base item in list responses
type KnowledgebaseListItem struct {
ID string `json:"id"`
Avatar *string `json:"avatar"`
Name string `json:"name"`
Language *string `json:"language"`
Description *string `json:"description"`
TenantID string `json:"tenant_id"`
Permission string `json:"permission"`
DocNum int64 `json:"doc_num"`
TokenNum int64 `json:"token_num"`
ChunkNum int64 `json:"chunk_num"`
ParserID string `json:"parser_id"`
ParserConfig JSONMap `json:"parser_config"`
Pagerank int64 `json:"pagerank"`
EmbdID string `json:"embd_id"`
TenantEmbdID *string `json:"tenant_embd_id,omitempty"`
Nickname string `json:"nickname"`
TenantAvatar *string `json:"tenant_avatar,omitempty"`
UpdateTime *int64 `json:"update_time,omitempty"`
ID string `json:"id"`
Avatar *string `json:"avatar"`
Name string `json:"name"`
Language *string `json:"language"`
Description *string `json:"description"`
TenantID string `json:"tenant_id"`
Permission string `json:"permission"`
DocNum int64 `json:"doc_num"`
TokenNum int64 `json:"token_num"`
ChunkNum int64 `json:"chunk_num"`
SimilarityThreshold float64 `json:"similarity_threshold"`
VectorSimilarityWeight float64 `json:"-"`
ParserID string `json:"parser_id"`
ParserConfig JSONMap `json:"parser_config"`
Pagerank int64 `json:"pagerank"`
EmbdID string `json:"embd_id"`
TenantEmbdID *string `json:"tenant_embd_id,omitempty"`
Nickname string `json:"nickname"`
TenantAvatar *string `json:"tenant_avatar,omitempty"`
UpdateTime *int64 `json:"update_time,omitempty"`
}
// DatasetOwnerFilter represents an owner option in dataset filter responses.
-1
View File
@@ -377,7 +377,6 @@ func (h *ChatHandler) GetChat(c *gin.Context) {
"meta_data_filter": chat.MetaDataFilter,
"keywords_similarity_weight": 1 - chat.VectorSimilarityWeight,
"similarity_threshold": chat.SimilarityThreshold,
"vector_similarity_weight": chat.VectorSimilarityWeight,
"top_n": chat.TopN,
"rerank_candidates_count": chat.RerankCandidatesCount,
"top_k": chat.TopK,
+2 -2
View File
@@ -236,7 +236,7 @@ func (h *SearchHandler) GetSearch(c *gin.Context) {
"created_by": search.CreatedBy,
"create_time": search.CreateTime,
"update_time": search.UpdateTime,
"search_config": search.SearchConfig,
"search_config": service.BuildSearchConfigResponse(search.SearchConfig),
}
if search.Avatar != nil {
@@ -360,7 +360,7 @@ func (h *SearchHandler) UpdateSearch(c *gin.Context) {
"status": updatedSearch.Status,
"create_time": updatedSearch.CreateTime,
"update_time": updatedSearch.UpdateTime,
"search_config": updatedSearch.SearchConfig,
"search_config": service.BuildSearchConfigResponse(updatedSearch.SearchConfig),
}
if updatedSearch.Avatar != nil {
+19 -1
View File
@@ -72,6 +72,24 @@ type ChatWithKBNames struct {
TenantAvatar *string `json:"tenant_avatar,omitempty"`
}
// MarshalJSON exposes the keyword weight while keeping the persisted vector
// weight internal to the service.
func (chat *ChatWithKBNames) MarshalJSON() ([]byte, error) {
data, err := structToMap(chat.Chat)
if err != nil {
return nil, err
}
data["kb_names"] = chat.KBNames
data["dataset_ids"] = chat.DatasetIDs
data["nickname"] = chat.Nickname
if chat.TenantAvatar != nil {
data["tenant_avatar"] = *chat.TenantAvatar
}
data["keywords_similarity_weight"] = 1 - chat.VectorSimilarityWeight
delete(data, "vector_similarity_weight")
return json.Marshal(data)
}
// ListChatsResponse list chats response
type ListChatsResponse struct {
Total int64 `json:"total"`
@@ -601,6 +619,7 @@ func (s *ChatService) buildCreateChatResponse(ctx context.Context, chat *entity.
data["kb_names"] = kbNames
data["meta_data_filter"] = normalizeMetaDataFilter(chat.MetaDataFilter)
data["keywords_similarity_weight"] = 1 - chat.VectorSimilarityWeight
delete(data, "vector_similarity_weight")
return data, nil
}
@@ -1161,7 +1180,6 @@ func (s *ChatService) buildRESTChatResponse(ctx context.Context, chat *entity.Ch
"prompt_config": chat.PromptConfig,
"meta_data_filter": normalizeMetaDataFilter(chat.MetaDataFilter),
"similarity_threshold": chat.SimilarityThreshold,
"vector_similarity_weight": chat.VectorSimilarityWeight,
"top_n": chat.TopN,
"keywords_similarity_weight": 1 - chat.VectorSimilarityWeight,
"rerank_candidates_count": chat.RerankCandidatesCount,
+27
View File
@@ -1,6 +1,7 @@
package service
import (
"encoding/json"
"math"
"strings"
"testing"
@@ -310,6 +311,32 @@ func TestChatServiceUpdateChatNormalizesKeywordsSimilarityWeight(t *testing.T) {
if got, ok := resp["keywords_similarity_weight"].(float64); !ok || math.Abs(got-0.7) > similarityWeightTolerance {
t.Fatalf("keywords_similarity_weight = %#v, want 0.7", resp["keywords_similarity_weight"])
}
if _, exists := resp["vector_similarity_weight"]; exists {
t.Fatal("response must not include vector_similarity_weight")
}
}
func TestChatWithKBNamesMarshalJSONOmitsVectorSimilarityWeight(t *testing.T) {
payload, err := json.Marshal(&ChatWithKBNames{
Chat: &entity.Chat{VectorSimilarityWeight: 0.3},
KBNames: []string{"Dataset"},
DatasetIDs: []string{"dataset-1"},
Nickname: "Owner",
})
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
response := map[string]interface{}{}
if err = json.Unmarshal(payload, &response); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if got, ok := response["keywords_similarity_weight"].(float64); !ok || math.Abs(got-0.7) > similarityWeightTolerance {
t.Fatalf("keywords_similarity_weight = %#v, want 0.7", response["keywords_similarity_weight"])
}
if _, exists := response["vector_similarity_weight"]; exists {
t.Fatal("response must not include vector_similarity_weight")
}
}
func TestChatServiceUpdateChatRejectsMismatchedSimilarityWeights(t *testing.T) {
+7
View File
@@ -2,6 +2,7 @@ package dataset
import (
"fmt"
"math"
"strings"
"testing"
@@ -61,6 +62,12 @@ func TestCreateDataset_NoComponentParams(t *testing.T) {
if result["parser_id"] != string(entity.ParserTypeGeneral) {
t.Fatalf("expected canonical parser_id %q, got %#v", entity.ParserTypeGeneral, result["parser_id"])
}
if got, ok := result["keywords_similarity_weight"].(float64); !ok || math.Abs(got-0.7) > 1e-9 {
t.Fatalf("keywords_similarity_weight = %#v, want 0.7", result["keywords_similarity_weight"])
}
if _, exists := result["vector_similarity_weight"]; exists {
t.Fatal("response must not include vector_similarity_weight")
}
}
func TestCreateDataset_DefaultsParentChildConfig(t *testing.T) {
+7
View File
@@ -1,6 +1,7 @@
package dataset
import (
"math"
"testing"
"ragflow/internal/common"
@@ -41,6 +42,12 @@ func TestDatasetServiceListDatasetsFiltersByIDs(t *testing.T) {
if data[0]["id"] != "kb-1" {
t.Fatalf("expected kb-1, got %#v", data[0]["id"])
}
if got, ok := data[0]["keywords_similarity_weight"].(float64); !ok || math.Abs(got-0.7) > 1e-9 {
t.Fatalf("keywords_similarity_weight = %#v, want 0.7", data[0]["keywords_similarity_weight"])
}
if _, exists := data[0]["vector_similarity_weight"]; exists {
t.Fatal("response must not include vector_similarity_weight")
}
}
func TestDatasetServiceListDatasetsIDsAccessibleViaTeamTenant(t *testing.T) {
+32 -30
View File
@@ -19,21 +19,23 @@ func datasetListItemToMap(kb *entity.KnowledgebaseListItem) map[string]interface
// avatar/language/description keys are always present (null when unset),
// matching Python's full-row dict response.
item := map[string]interface{}{
"id": kb.ID,
"name": kb.Name,
"avatar": stringPointerValue(kb.Avatar),
"language": stringPointerValue(kb.Language),
"description": stringPointerValue(kb.Description),
"tenant_id": kb.TenantID,
"permission": kb.Permission,
"document_count": kb.DocNum,
"token_num": kb.TokenNum,
"chunk_count": kb.ChunkNum,
"parser_id": datasetParserIDForResponse(kb.ParserID),
"parser_config": jsonMapValue(kb.ParserConfig),
"pagerank": kb.Pagerank,
"embedding_model": kb.EmbdID,
"nickname": kb.Nickname,
"id": kb.ID,
"name": kb.Name,
"avatar": stringPointerValue(kb.Avatar),
"language": stringPointerValue(kb.Language),
"description": stringPointerValue(kb.Description),
"tenant_id": kb.TenantID,
"permission": kb.Permission,
"document_count": kb.DocNum,
"token_num": kb.TokenNum,
"chunk_count": kb.ChunkNum,
"similarity_threshold": kb.SimilarityThreshold,
"keywords_similarity_weight": 1 - kb.VectorSimilarityWeight,
"parser_id": datasetParserIDForResponse(kb.ParserID),
"parser_config": jsonMapValue(kb.ParserConfig),
"pagerank": kb.Pagerank,
"embedding_model": kb.EmbdID,
"nickname": kb.Nickname,
}
if kb.TenantAvatar != nil {
item["tenant_avatar"] = *kb.TenantAvatar
@@ -46,21 +48,21 @@ func datasetListItemToMap(kb *entity.KnowledgebaseListItem) map[string]interface
func datasetToMap(kb *entity.Knowledgebase) map[string]interface{} {
item := map[string]interface{}{
"id": kb.ID,
"tenant_id": kb.TenantID,
"name": kb.Name,
"embedding_model": kb.EmbdID,
"permission": kb.Permission,
"created_by": kb.CreatedBy,
"document_count": kb.DocNum,
"token_num": kb.TokenNum,
"chunk_count": kb.ChunkNum,
"similarity_threshold": kb.SimilarityThreshold,
"vector_similarity_weight": kb.VectorSimilarityWeight,
"parser_id": datasetParserIDForResponse(kb.ParserID),
"parser_config": kb.ParserConfig,
"pagerank": kb.Pagerank,
"create_time": kb.CreateTime,
"id": kb.ID,
"tenant_id": kb.TenantID,
"name": kb.Name,
"embedding_model": kb.EmbdID,
"permission": kb.Permission,
"created_by": kb.CreatedBy,
"document_count": kb.DocNum,
"token_num": kb.TokenNum,
"chunk_count": kb.ChunkNum,
"similarity_threshold": kb.SimilarityThreshold,
"keywords_similarity_weight": 1 - kb.VectorSimilarityWeight,
"parser_id": datasetParserIDForResponse(kb.ParserID),
"parser_config": kb.ParserConfig,
"pagerank": kb.Pagerank,
"create_time": kb.CreateTime,
}
if kb.Avatar != nil {
item["avatar"] = *kb.Avatar
+18 -2
View File
@@ -177,7 +177,7 @@ func (s *SearchService) toSearchAppResponse(search *entity.SearchListItem) map[s
"status": search.Status,
"create_time": search.CreateTime,
"update_time": search.UpdateTime,
"search_config": map[string]interface{}(search.SearchConfig),
"search_config": BuildSearchConfigResponse(map[string]interface{}(search.SearchConfig)),
"nickname": ownerNickname(search.Nickname, search.TenantID),
}
@@ -311,7 +311,7 @@ func (s *SearchService) GetSearchShareDetail(ctx context.Context, userID, search
Name: detail.Name,
Description: detail.Description,
CreatedBy: detail.CreatedBy,
SearchConfig: detail.SearchConfig,
SearchConfig: BuildSearchConfigResponse(detail.SearchConfig),
UpdateTime: detail.UpdateTime,
}, nil
}
@@ -574,6 +574,22 @@ func floatFromSearchConfig(value interface{}) (float64, bool) {
}
}
// BuildSearchConfigResponse returns the public search configuration without
// exposing the internally persisted vector weight.
func BuildSearchConfigResponse(searchConfig map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{}, len(searchConfig))
for key, value := range searchConfig {
result[key] = value
}
if _, exists := result["keywords_similarity_weight"]; !exists {
if vectorWeight, ok := floatFromSearchConfig(result["vector_similarity_weight"]); ok {
result["keywords_similarity_weight"] = 1 - vectorWeight
}
}
delete(result, "vector_similarity_weight")
return result
}
// UpdateSearchRequest update search request
// Reference: api/apps/restful_apis/search_api.py::update
// Required fields: name, search_config
+18
View File
@@ -131,6 +131,24 @@ func TestSearchServiceCreateAndUpdateRoundTrip(t *testing.T) {
}
}
func TestBuildSearchConfigResponseOmitsVectorSimilarityWeight(t *testing.T) {
searchConfig := map[string]interface{}{
"similarity_threshold": 0.2,
"vector_similarity_weight": 0.3,
}
response := BuildSearchConfigResponse(searchConfig)
if got, ok := response["keywords_similarity_weight"].(float64); !ok || math.Abs(got-0.7) > similarityWeightTolerance {
t.Fatalf("keywords_similarity_weight = %#v, want 0.7", response["keywords_similarity_weight"])
}
if _, exists := response["vector_similarity_weight"]; exists {
t.Fatal("response must not include vector_similarity_weight")
}
if _, exists := searchConfig["keywords_similarity_weight"]; exists {
t.Fatal("BuildSearchConfigResponse must not mutate the stored search config")
}
}
func TestSearchServiceListSearchesReturnsOwnerDisplayFields(t *testing.T) {
setupSearchServiceTestDB(t)
-1
View File
@@ -82,7 +82,6 @@ export interface IDialog {
update_date: string;
update_time: number;
keywords_similarity_weight?: number;
vector_similarity_weight?: number;
similarity_threshold: number;
top_k: number;
top_n: number;
+1 -1
View File
@@ -48,7 +48,7 @@ export interface IDataset {
token_num: number;
update_date: string;
update_time: number;
vector_similarity_weight: number;
keywords_similarity_weight: number;
connectors: IConnector[];
}
@@ -134,7 +134,6 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
'update_date',
'id',
'top_k',
'vector_similarity_weight',
]),
...nextValues,
},
@@ -153,11 +152,9 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
? { ...referenceMetadata, fields: undefined }
: referenceMetadata;
const keywordsSimilarityWeight =
data.keywords_similarity_weight ??
1 - (data.vector_similarity_weight ?? 0.3);
const keywordsSimilarityWeight = data.keywords_similarity_weight ?? 0.7;
const nextData = {
...omit(data, ['top_k', 'vector_similarity_weight']),
...omit(data, ['top_k']),
prompt_config: {
...data.prompt_config,
// reset() skips undefined values, so fall back to '' to clear the field
+1 -2
View File
@@ -104,8 +104,7 @@ function SearchSetting({
search_config: {
kb_ids: search_config?.kb_ids || [],
keywords_similarity_weight:
search_config?.keywords_similarity_weight ??
1 - (search_config?.vector_similarity_weight ?? 0.3),
search_config?.keywords_similarity_weight ?? 0.7,
web_search: search_config?.web_search || false,
doc_ids: [],
similarity_threshold: search_config?.similarity_threshold ?? 0.2,
-1
View File
@@ -216,7 +216,6 @@ export interface ISearchAppDetailProps {
rerank_candidates_count: number;
use_kg: boolean;
keywords_similarity_weight?: number;
vector_similarity_weight?: number;
web_search: boolean;
chat_settingcross_languages: string[];
meta_data_filter?: {