mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-07 20:10:14 +08:00
214 lines
5.6 KiB
Go
214 lines
5.6 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"ragflow/internal/entity"
|
|
modelModule "ragflow/internal/entity/models"
|
|
)
|
|
|
|
// GenerateRelatedQuestions generates related search questions for chat/searchbot endpoints.
|
|
func GenerateRelatedQuestions(tenantID, question, searchID string, searchSvc *SearchService, tenantSvc *TenantService, modelProviderSvc *ModelProviderService) ([]string, error) {
|
|
if modelProviderSvc == nil {
|
|
return nil, fmt.Errorf("model provider service not configured")
|
|
}
|
|
searchConfig := relatedQuestionsSearchConfig(searchID, searchSvc)
|
|
modelID := relatedQuestionsModelID(tenantID, searchConfig, tenantSvc)
|
|
prompt, err := LoadPrompt("related_question")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
messages := []modelModule.Message{
|
|
{Role: "system", Content: prompt},
|
|
{Role: "user", Content: "\nKeywords: " + question + "\nRelated search terms:\n "},
|
|
}
|
|
response, err := modelProviderSvc.Chat(tenantID, modelID, messages, relatedQuestionsConfig(searchConfig))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if response != nil && response.Answer != nil {
|
|
return parseRelatedQuestions(*response.Answer), nil
|
|
}
|
|
return []string{}, nil
|
|
}
|
|
|
|
func relatedQuestionsSearchConfig(searchID string, searchSvc *SearchService) map[string]interface{} {
|
|
if searchID == "" || searchSvc == nil {
|
|
return map[string]interface{}{}
|
|
}
|
|
if detail, err := searchSvc.GetDetail(searchID); err == nil && detail != nil {
|
|
return relatedQuestionsSearchConfigFromDetail(detail)
|
|
}
|
|
return map[string]interface{}{}
|
|
}
|
|
|
|
func relatedQuestionsSearchConfigFromDetail(detail map[string]interface{}) map[string]interface{} {
|
|
if sc, ok := detail["search_config"].(map[string]interface{}); ok && sc != nil {
|
|
return sc
|
|
}
|
|
if sc, ok := detail["search_config"].(entity.JSONMap); ok && sc != nil {
|
|
return map[string]interface{}(sc)
|
|
}
|
|
return map[string]interface{}{}
|
|
}
|
|
|
|
func relatedQuestionsModelID(tenantID string, searchConfig map[string]interface{}, tenantSvc *TenantService) string {
|
|
modelID, _ := searchConfig["chat_id"].(string)
|
|
if modelID != "" || tenantSvc == nil {
|
|
return modelID
|
|
}
|
|
defaultModel, err := tenantSvc.GetDefaultModelName(tenantID, entity.ModelTypeChat)
|
|
if err == nil {
|
|
modelID = defaultModel
|
|
}
|
|
return modelID
|
|
}
|
|
|
|
func relatedQuestionsConfig(searchConfig map[string]interface{}) *modelModule.ChatConfig {
|
|
var genConf map[string]interface{}
|
|
switch v := searchConfig["llm_setting"].(type) {
|
|
case map[string]interface{}:
|
|
genConf = v
|
|
case entity.JSONMap:
|
|
genConf = map[string]interface{}(v)
|
|
}
|
|
if genConf == nil {
|
|
return &modelModule.ChatConfig{Temperature: float64Ptr(0.9)}
|
|
}
|
|
cfg := &modelModule.ChatConfig{}
|
|
for key, value := range genConf {
|
|
if key == "parameter" {
|
|
continue
|
|
}
|
|
switch key {
|
|
case "stream":
|
|
if v, ok := value.(bool); ok {
|
|
cfg.Stream = &v
|
|
}
|
|
case "thinking":
|
|
if v, ok := value.(bool); ok {
|
|
cfg.Thinking = &v
|
|
}
|
|
case "max_tokens":
|
|
if v, ok := intFromRelatedQuestionConfig(value); ok {
|
|
cfg.MaxTokens = &v
|
|
}
|
|
case "temperature":
|
|
if v, ok := floatFromRelatedQuestionConfig(value); ok {
|
|
cfg.Temperature = &v
|
|
}
|
|
case "top_p":
|
|
if v, ok := floatFromRelatedQuestionConfig(value); ok && v > 0 {
|
|
cfg.TopP = &v
|
|
}
|
|
case "do_sample":
|
|
if v, ok := value.(bool); ok {
|
|
cfg.DoSample = &v
|
|
}
|
|
case "stop":
|
|
if stops := stringSliceFromRelatedQuestionConfig(value); len(stops) > 0 {
|
|
cfg.Stop = &stops
|
|
}
|
|
case "model_class":
|
|
if v, ok := value.(string); ok {
|
|
cfg.ModelClass = &v
|
|
}
|
|
case "effort":
|
|
if v, ok := value.(string); ok {
|
|
cfg.Effort = &v
|
|
}
|
|
case "verbosity":
|
|
if v, ok := value.(string); ok {
|
|
cfg.Verbosity = &v
|
|
}
|
|
}
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func intFromRelatedQuestionConfig(value interface{}) (int, bool) {
|
|
switch v := value.(type) {
|
|
case int:
|
|
return v, true
|
|
case int64:
|
|
return int(v), true
|
|
case float64:
|
|
return int(v), true
|
|
case json.Number:
|
|
if n, err := v.Int64(); err == nil {
|
|
return int(n), true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func floatFromRelatedQuestionConfig(value interface{}) (float64, bool) {
|
|
switch v := value.(type) {
|
|
case float64:
|
|
return v, true
|
|
case float32:
|
|
return float64(v), true
|
|
case int:
|
|
return float64(v), true
|
|
case int64:
|
|
return float64(v), true
|
|
case json.Number:
|
|
if n, err := v.Float64(); err == nil {
|
|
return n, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func stringSliceFromRelatedQuestionConfig(value interface{}) []string {
|
|
switch v := value.(type) {
|
|
case []string:
|
|
return v
|
|
case []interface{}:
|
|
result := make([]string, 0, len(v))
|
|
for _, item := range v {
|
|
if s, ok := item.(string); ok {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var relatedQuestionLineRe = regexp.MustCompile(`^\d+\.\s`)
|
|
|
|
func parseRelatedQuestions(text string) []string {
|
|
var result []string
|
|
for _, line := range strings.Split(text, "\n") {
|
|
if relatedQuestionLineRe.MatchString(line) {
|
|
result = append(result, relatedQuestionLineRe.ReplaceAllString(line, ""))
|
|
}
|
|
}
|
|
if result == nil {
|
|
return []string{}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func float64Ptr(v float64) *float64 { return &v }
|