From e5d217993b02a9d558aa3ff78bd11c965ee0fdc9 Mon Sep 17 00:00:00 2001 From: Hz_ Date: Mon, 6 Jul 2026 10:05:34 +0800 Subject: [PATCH] fix(go-skill): Elasticsearch skill search field mapping (#16611) ## Summary Fix Elasticsearch-backed skill search by mapping skill search fields to their indexed token fields. `name`, `tags`, `description`, and `content` are stored for display but are not searchable in the skill ES mapping. Search queries now target `name_tks`, `tags_tks`, `description_tks`, and `content_tks`. ## Testing - Ran Go unit tests: ```bash /usr/local/go/bin/go test -count=1 ./internal/engine/elasticsearch ``` - Frontend verification: 1. Open /files/skills. 2. Enter a skill space. 3. Reindex the skill space if existing skills were created before this fix. 4. Search by skill name or description keyword. 5. Confirm matching skills are returned. --- internal/engine/elasticsearch/chunk.go | 26 +++++++++++++++++ .../elasticsearch/chunk_helpers_test.go | 29 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/internal/engine/elasticsearch/chunk.go b/internal/engine/elasticsearch/chunk.go index 93a6554b54..0bfe46428a 100644 --- a/internal/engine/elasticsearch/chunk.go +++ b/internal/engine/elasticsearch/chunk.go @@ -1746,6 +1746,9 @@ func buildQueryStringQuery(matchText *types.MatchTextExpr, vectorSimilarityWeigh fields = []string{"title_tks^10", "title_sm_tks^5", "important_kwd^30", "important_tks^20", "question_tks^20", "content_ltks^2", "content_sm_ltks"} } } + if isSkillIndex { + fields = mapSkillSearchFields(fields) + } if isMemoryIndex { fields = mapMemoryMessageESFields(fields, true) } @@ -1768,6 +1771,29 @@ func buildQueryStringQuery(matchText *types.MatchTextExpr, vectorSimilarityWeigh } } +func mapSkillSearchFields(fields []string) []string { + mapped := make([]string, 0, len(fields)) + for _, field := range fields { + name, boost, hasBoost := strings.Cut(field, "^") + switch name { + case "name": + name = "name_tks" + case "tags": + name = "tags_tks" + case "description": + name = "description_tks" + case "content": + name = "content_tks" + } + if hasBoost { + mapped = append(mapped, name+"^"+boost) + } else { + mapped = append(mapped, name) + } + } + return mapped +} + // buildRankFeatureQuery builds rank_feature queries for learning to rank func buildRankFeatureQuery(rankFeature map[string]float64) []map[string]interface{} { if rankFeature == nil || len(rankFeature) == 0 { diff --git a/internal/engine/elasticsearch/chunk_helpers_test.go b/internal/engine/elasticsearch/chunk_helpers_test.go index d3d7d9de8a..fbbb71842c 100644 --- a/internal/engine/elasticsearch/chunk_helpers_test.go +++ b/internal/engine/elasticsearch/chunk_helpers_test.go @@ -4,8 +4,37 @@ import ( "reflect" "strings" "testing" + + "ragflow/internal/engine/types" ) +func TestBuildQueryStringQueryMapsSkillFieldsToTokenFields(t *testing.T) { + query := buildQueryStringQuery(&types.MatchTextExpr{ + MatchingText: "test", + Fields: []string{"name^10", "tags^5", "description^3", "content^1"}, + }, 0, true, false) + + queryString, ok := query["query_string"].(map[string]interface{}) + if !ok { + t.Fatalf("query_string missing from %#v", query) + } + assertEqual(t, queryString["fields"], []string{"name_tks^10", "tags_tks^5", "description_tks^3", "content_tks^1"}) + assertEqual(t, queryString["query"], "test") +} + +func TestBuildQueryStringQueryKeepsDocumentFieldsUnchanged(t *testing.T) { + query := buildQueryStringQuery(&types.MatchTextExpr{ + MatchingText: "test", + Fields: []string{"name^10"}, + }, 0, false, false) + + queryString, ok := query["query_string"].(map[string]interface{}) + if !ok { + t.Fatalf("query_string missing from %#v", query) + } + assertEqual(t, queryString["fields"], []string{"name^10"}) +} + func TestElasticsearchGetFieldsFiltersAndUsesIDFallback(t *testing.T) { engine := &elasticsearchEngine{} chunks := []map[string]interface{}{