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.
This commit is contained in:
Hz_
2026-07-06 10:05:34 +08:00
committed by GitHub
parent 0265ffbc53
commit e5d217993b
2 changed files with 55 additions and 0 deletions

View File

@@ -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 {

View File

@@ -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{}{