diff --git a/docs/references/http_api_reference.md b/docs/references/http_api_reference.md index 88da403e30..afb8824946 100644 --- a/docs/references/http_api_reference.md +++ b/docs/references/http_api_reference.md @@ -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", diff --git a/internal/dao/kb.go b/internal/dao/kb.go index cdbd3f4c59..6f40e9d2d7 100644 --- a/internal/dao/kb.go +++ b/internal/dao/kb.go @@ -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`). diff --git a/internal/entity/dataset.go b/internal/entity/dataset.go index c8cc953897..63b101e6f3 100644 --- a/internal/entity/dataset.go +++ b/internal/entity/dataset.go @@ -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. diff --git a/internal/handler/chat.go b/internal/handler/chat.go index 238c0af206..18571b0b45 100644 --- a/internal/handler/chat.go +++ b/internal/handler/chat.go @@ -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, diff --git a/internal/handler/search.go b/internal/handler/search.go index 7677558111..dd74758352 100644 --- a/internal/handler/search.go +++ b/internal/handler/search.go @@ -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 { diff --git a/internal/service/chat.go b/internal/service/chat.go index 7e1b70a5f8..d0227b0624 100644 --- a/internal/service/chat.go +++ b/internal/service/chat.go @@ -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, diff --git a/internal/service/chat_rest_update_test.go b/internal/service/chat_rest_update_test.go index 7ce8fbcadb..1af96885a1 100644 --- a/internal/service/chat_rest_update_test.go +++ b/internal/service/chat_rest_update_test.go @@ -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) { diff --git a/internal/service/dataset/create_test.go b/internal/service/dataset/create_test.go index e59f32f2df..791d466960 100644 --- a/internal/service/dataset/create_test.go +++ b/internal/service/dataset/create_test.go @@ -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) { diff --git a/internal/service/dataset/list_test.go b/internal/service/dataset/list_test.go index 6b3bf3de2d..00e78ce726 100644 --- a/internal/service/dataset/list_test.go +++ b/internal/service/dataset/list_test.go @@ -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) { diff --git a/internal/service/dataset/utils.go b/internal/service/dataset/utils.go index acc7c3b35b..7459d99ee7 100644 --- a/internal/service/dataset/utils.go +++ b/internal/service/dataset/utils.go @@ -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 diff --git a/internal/service/search.go b/internal/service/search.go index 9be3589618..000c954f58 100644 --- a/internal/service/search.go +++ b/internal/service/search.go @@ -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 diff --git a/internal/service/search_test.go b/internal/service/search_test.go index 1bc640462c..d167174875 100644 --- a/internal/service/search_test.go +++ b/internal/service/search_test.go @@ -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) diff --git a/web/src/interfaces/database/chat.ts b/web/src/interfaces/database/chat.ts index 5d4a02db9e..84a3b50ad5 100644 --- a/web/src/interfaces/database/chat.ts +++ b/web/src/interfaces/database/chat.ts @@ -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; diff --git a/web/src/interfaces/database/dataset.ts b/web/src/interfaces/database/dataset.ts index e9b144c486..434145e992 100644 --- a/web/src/interfaces/database/dataset.ts +++ b/web/src/interfaces/database/dataset.ts @@ -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[]; } diff --git a/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx b/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx index ca531a3973..82ce9598f5 100644 --- a/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx +++ b/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx @@ -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 diff --git a/web/src/pages/next-search/search-setting.tsx b/web/src/pages/next-search/search-setting.tsx index 143c3a2681..84dadd7d44 100644 --- a/web/src/pages/next-search/search-setting.tsx +++ b/web/src/pages/next-search/search-setting.tsx @@ -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, diff --git a/web/src/pages/next-searches/hooks.ts b/web/src/pages/next-searches/hooks.ts index 5087005cbf..4797d73379 100644 --- a/web/src/pages/next-searches/hooks.ts +++ b/web/src/pages/next-searches/hooks.ts @@ -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?: {