Use GetChatModel, remove duplicate functions in model_service.go (#14546)

### What problem does this PR solve?

Use GetChatModel, remove duplicate functions in model_service.go

### Type of change

- [x] Refactoring

Co-authored-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
qinling0210
2026-05-06 11:33:32 +08:00
committed by GitHub
parent 9e4f3614de
commit 7335916868
8 changed files with 138 additions and 200 deletions

View File

@@ -1322,10 +1322,6 @@ func (p *Parser) parseCreateProviderInstance() (*Command, error) {
return nil, fmt.Errorf("expected instance name: %w", err)
}
// Check if instance_name is "default"
if instanceName == "default" {
return nil, fmt.Errorf("instance name cannot be 'default'")
}
p.nextToken()
if p.curToken.Type != TokenKey {

View File

@@ -35,10 +35,3 @@ const (
// ModelTypeOCR optical character recognition model
ModelTypeOCR ModelType = "ocr"
)
// ModelCredentials holds the credentials for a model
type ModelCredentials struct {
ProviderName string
ModelName string
APIKey string
}

View File

@@ -265,15 +265,6 @@ func (h *ProviderHandler) CreateProviderInstance(c *gin.Context) {
return
}
// Check if instance name is "default"
if req.InstanceName == "default" {
c.JSON(http.StatusOK, gin.H{
"code": common.CodeBadRequest,
"message": "Instance name cannot be 'default'",
})
return
}
userID := c.GetString("user_id")
_, err := h.modelProviderService.CreateProviderInstance(providerName, req.InstanceName, req.APIKey, req.BaseURL, req.Region, userID)

View File

@@ -192,7 +192,7 @@ func (s *ChunkService) RetrievalTest(req *RetrievalTestRequest, userID string) (
// Determine meta_data_filter
var chatID string
var creds *entity.ModelCredentials
var chatModelForFilter *models.ChatModel
filter := req.Filter
if req.SearchID != nil && *req.SearchID != "" {
@@ -216,28 +216,32 @@ func (s *ChunkService) RetrievalTest(req *RetrievalTestRequest, userID string) (
if method == "auto" || method == "semi_auto" {
modelProviderSvc := NewModelProviderService()
if chatID != "" {
// Use chat_id from search_config
creds, err = modelProviderSvc.GetModelByName(chatID, tenantIDs[0])
// Use chat_id from search_config (it's actually the model name)
chatModelForFilter, err = modelProviderSvc.GetChatModel(tenantIDs[0], chatID)
if err != nil {
common.Warn("Failed to get chat model from search_config chat_id, using tenant default", zap.String("chatID", chatID), zap.Error(err))
} else {
common.Info("Fetched chat model (from search_config) for metadata filter",
zap.String("chatID", chatID),
zap.String("tenantID", tenantIDs[0]),
zap.String("providerName", creds.ProviderName),
zap.String("modelName", creds.ModelName))
zap.String("tenantID", tenantIDs[0]))
}
}
// If no chatID from search_config, or creds not found, use tenant default
if creds == nil {
creds, err = modelProviderSvc.GetDefaultModel(entity.ModelTypeChat, tenantIDs[0])
if err != nil {
common.Warn("Failed to get tenant default chat model for meta_data_filter", zap.Error(err))
// If no chatID from search_config, or chatModel not found, use tenant default
if chatModelForFilter == nil {
tenantSvc := NewTenantService()
modelName, err := tenantSvc.GetDefaultModelName(tenantIDs[0], entity.ModelTypeChat)
if err != nil || modelName == "" {
common.Warn("Failed to get tenant default chat model name for meta_data_filter", zap.Error(err))
} else {
common.Info("Fetched chat model (tenant default) for metadata filter",
zap.String("tenantID", tenantIDs[0]),
zap.String("providerName", creds.ProviderName),
zap.String("modelName", creds.ModelName))
chatModelForFilter, err = modelProviderSvc.GetChatModel(tenantIDs[0], modelName)
if err != nil {
common.Warn("Failed to get chat model for meta_data_filter", zap.Error(err))
} else {
common.Info("Fetched chat model (tenant default) for metadata filter",
zap.String("tenantID", tenantIDs[0]),
zap.String("modelName", modelName))
}
}
}
}
@@ -254,7 +258,7 @@ func (s *ChunkService) RetrievalTest(req *RetrievalTestRequest, userID string) (
common.Warn("Failed to get flatted metadata", zap.Error(err))
} else {
common.Info("metadata filter conditions", zap.Any("filter", filter))
filteredDocIDs, _ := ApplyMetaDataFilter(ctx, filter, flattedMeta, req.Question, creds, req.DocIDs)
filteredDocIDs, _ := ApplyMetaDataFilter(ctx, filter, flattedMeta, req.Question, chatModelForFilter, req.DocIDs)
docIDs = filteredDocIDs
common.Info("ApplyMetaDataFilter result", zap.Strings("docIDs", docIDs))
}
@@ -262,24 +266,30 @@ func (s *ChunkService) RetrievalTest(req *RetrievalTestRequest, userID string) (
// Apply cross_languages and keyword extraction with tenant default chat model
modifiedQuestion := req.Question
var chatModel *models.ChatModel
// Get chat model for cross_languages and keyword_extraction
if len(req.CrossLanguages) > 0 || (req.Keyword != nil && *req.Keyword) {
tenantSvc := NewTenantService()
modelProviderSvc := NewModelProviderService()
creds, err = modelProviderSvc.GetDefaultModel(entity.ModelTypeChat, tenantIDs[0])
if err != nil {
common.Warn("Failed to get default chat model for LLM transformations", zap.Error(err))
modelName, err := tenantSvc.GetDefaultModelName(tenantIDs[0], "chat")
if err != nil || modelName == "" {
common.Warn("Failed to get default chat model name for LLM transformations", zap.Error(err))
} else {
common.Info("Fetched chat model (tenant default) for cross_languages/keyword_extraction",
zap.String("tenantID", tenantIDs[0]),
zap.String("providerName", creds.ProviderName),
zap.String("modelName", creds.ModelName))
chatModel, err = modelProviderSvc.GetChatModel(tenantIDs[0], modelName)
if err != nil {
common.Warn("Failed to get chat model for LLM transformations", zap.Error(err))
} else {
common.Info("Fetched chat model (tenant default) for cross_languages/keyword_extraction",
zap.String("tenantID", tenantIDs[0]),
zap.String("modelName", modelName))
}
}
}
// Apply cross_languages on the question (translate question)
if creds != nil && len(req.CrossLanguages) > 0 {
translated, err := CrossLanguages(ctx, creds, req.Question, req.CrossLanguages)
if chatModel != nil && len(req.CrossLanguages) > 0 {
translated, err := CrossLanguages(ctx, chatModel, req.Question, req.CrossLanguages)
if err != nil {
common.Warn("Failed to translate question", zap.Error(err))
} else {
@@ -288,8 +298,8 @@ func (s *ChunkService) RetrievalTest(req *RetrievalTestRequest, userID string) (
}
// Apply keyword extraction on the question (append keywords to question)
if creds != nil && req.Keyword != nil && *req.Keyword {
extractedKeywords, err := KeywordExtraction(ctx, creds, modifiedQuestion, 3)
if chatModel != nil && req.Keyword != nil && *req.Keyword {
extractedKeywords, err := KeywordExtraction(ctx, chatModel, modifiedQuestion, 3)
if err != nil {
common.Warn("Failed to extract keywords from question", zap.Error(err))
} else if extractedKeywords != "" {

View File

@@ -25,18 +25,17 @@ import (
"go.uber.org/zap"
"ragflow/internal/entity"
modelModule "ragflow/internal/entity/models"
)
// KeywordExtraction extracts keywords from content using LLM.
// Corresponds to rag/prompts/generator.py:keyword_extraction().
//
// Uses ChatToModelByApiKey via ModelCredentials to call the LLM with a keyword extraction prompt.
// Uses ChatModel to call the LLM with a keyword extraction prompt.
// Returns comma-separated top N important keywords/phrases from the content.
func KeywordExtraction(ctx context.Context, creds *entity.ModelCredentials, content string, topN int) (string, error) {
if creds == nil {
return "", fmt.Errorf("model credentials is nil")
func KeywordExtraction(ctx context.Context, chatModel *modelModule.ChatModel, content string, topN int) (string, error) {
if chatModel == nil {
return "", fmt.Errorf("chat model is nil")
}
if content == "" {
@@ -65,32 +64,39 @@ func KeywordExtraction(ctx context.Context, creds *entity.ModelCredentials, cont
{Role: "user", Content: "Output: "},
}
// Call LLM using ChatWithMessagesToModelByApiKey
modelProviderSvc := NewModelProviderService()
responsePtr, code, err := modelProviderSvc.ChatWithMessagesToModelByApiKey(creds.ProviderName, creds.ModelName, creds.APIKey, messages)
if err != nil {
return "", fmt.Errorf("failed to extract keywords: code=%d, err=%w", int(code), err)
// Use low temperature for deterministic keyword extraction (matching Python behavior)
modelConfig := &modelModule.ChatConfig{
Temperature: func() *float64 { t := 0.2; return &t }(),
}
response := *responsePtr
common.Info("KeywordExtraction result", zap.String("response", response))
// Call LLM using ChatModel
response, err := chatModel.ModelDriver.ChatWithMessages(*chatModel.ModelName, messages, chatModel.APIConfig, modelConfig)
if err != nil {
return "", fmt.Errorf("failed to extract keywords: %w", err)
}
if response == nil || response.Answer == nil {
return "", fmt.Errorf("empty response from keyword extraction")
}
common.Info("KeywordExtraction result", zap.String("response", *response.Answer))
// Clean up response - remove thinking tags if present
response = strings.TrimSpace(response)
response = thinkBlockRE.ReplaceAllString(response, "")
response = strings.TrimSpace(response)
result := strings.TrimSpace(*response.Answer)
result = thinkBlockRE.ReplaceAllString(result, "")
result = strings.TrimSpace(result)
if strings.Contains(response, "**ERROR**") {
if strings.Contains(result, "**ERROR**") {
return "", fmt.Errorf("error in keyword extraction response")
}
return response, nil
return result, nil
}
// CrossLanguages translates a question into multiple languages using LLM.
func CrossLanguages(ctx context.Context, creds *entity.ModelCredentials, query string, languages []string) (string, error) {
if creds == nil {
return "", fmt.Errorf("model credentials is nil")
func CrossLanguages(ctx context.Context, chatModel *modelModule.ChatModel, query string, languages []string) (string, error) {
if chatModel == nil {
return "", fmt.Errorf("chat model is nil")
}
if query == "" {
@@ -125,32 +131,40 @@ func CrossLanguages(ctx context.Context, creds *entity.ModelCredentials, query s
{Role: "user", Content: userPrompt},
}
// Call LLM using ChatWithMessagesToModelByApiKey
modelProviderSvc := NewModelProviderService()
responsePtr, code, err := modelProviderSvc.ChatWithMessagesToModelByApiKey(creds.ProviderName, creds.ModelName, creds.APIKey, messages)
if err != nil {
return query, fmt.Errorf("failed to translate question: code=%d, err=%w", int(code), err)
// Use low temperature for deterministic translation (matching Python behavior)
modelConfig := &modelModule.ChatConfig{
Temperature: func() *float64 { t := 0.2; return &t }(),
}
response := *responsePtr
// Call LLM using ChatModel
response, err := chatModel.ModelDriver.ChatWithMessages(*chatModel.ModelName, messages, chatModel.APIConfig, modelConfig)
if err != nil {
return query, fmt.Errorf("failed to translate question: %w", err)
}
if response == nil || response.Answer == nil {
return query, fmt.Errorf("empty response from cross languages translation")
}
result := *response.Answer
// Clean up response - remove think tags and trim
response = strings.TrimSpace(response)
response = thinkBlockRE.ReplaceAllString(response, "")
response = strings.TrimSpace(response)
result = strings.TrimSpace(result)
result = thinkBlockRE.ReplaceAllString(result, "")
result = strings.TrimSpace(result)
if strings.Contains(response, "**ERROR**") {
if strings.Contains(result, "**ERROR**") {
return query, nil
}
// Parse response
response = strings.TrimPrefix(response, "Output:")
response = strings.TrimPrefix(response, "output:")
response = regexp.MustCompile(`(?i)^output:\s*`).ReplaceAllString(response, "")
response = regexp.MustCompile(`\n+`).ReplaceAllString(response, "")
response = strings.TrimSpace(response)
result = strings.TrimPrefix(result, "Output:")
result = strings.TrimPrefix(result, "output:")
result = regexp.MustCompile(`(?i)^output:\s*`).ReplaceAllString(result, "")
result = regexp.MustCompile(`\n+`).ReplaceAllString(result, "")
result = strings.TrimSpace(result)
parts := strings.Split(response, "===")
parts := strings.Split(result, "===")
var translations []string
for _, part := range parts {
trimmed := strings.TrimSpace(part)

View File

@@ -28,7 +28,6 @@ import (
"go.uber.org/zap"
"ragflow/internal/entity"
modelModule "ragflow/internal/entity/models"
)
@@ -123,9 +122,9 @@ func genMetaFilterPrompt(metaDataJSON, question, constraintsJSON, currentDate st
}
// GenMetaFilter generates filter conditions using LLM based on metadata and question.
func GenMetaFilter(ctx context.Context, creds *entity.ModelCredentials, metaData map[string]interface{}, question string, constraints map[string]string) (*MetaFilterResult, error) {
if creds == nil {
return nil, fmt.Errorf("model credentials is nil")
func GenMetaFilter(ctx context.Context, chatModel *modelModule.ChatModel, metaData map[string]interface{}, question string, constraints map[string]string) (*MetaFilterResult, error) {
if chatModel == nil {
return nil, fmt.Errorf("chat model is nil")
}
if len(metaData) == 0 {
@@ -164,20 +163,23 @@ func GenMetaFilter(ctx context.Context, creds *entity.ModelCredentials, metaData
{Role: "user", Content: userMessage},
}
// Call LLM using ChatWithMessagesToModelByApiKey
modelProviderSvc := NewModelProviderService()
response, code, err := modelProviderSvc.ChatWithMessagesToModelByApiKey(creds.ProviderName, creds.ModelName, creds.APIKey, messages)
// Call LLM using ChatModel
response, err := chatModel.ModelDriver.ChatWithMessages(*chatModel.ModelName, messages, chatModel.APIConfig, nil)
if err != nil {
common.Warn("ChatWithMessagesToModelByApiKey failed for GenMetaFilter",
zap.String("provider", creds.ProviderName),
zap.String("model", creds.ModelName),
zap.Int("code", int(code)),
common.Warn("ChatWithMessages failed for GenMetaFilter",
zap.String("model",
*chatModel.ModelName),
zap.Error(err))
return nil, fmt.Errorf("failed to generate meta filter: %w", err)
}
if response == nil || response.Answer == nil {
return nil, fmt.Errorf("empty response from meta filter generation")
}
// Clean up response
responseStr := strings.TrimSpace(*response)
responseStr := strings.TrimSpace(*response.Answer)
responseStr = thinkBlockRE.ReplaceAllString(responseStr, "")
responseStr = strings.TrimSpace(responseStr)
@@ -447,7 +449,7 @@ func ApplyMetaDataFilter(
metaDataFilter map[string]interface{},
metaData map[string]interface{},
question string,
creds *entity.ModelCredentials,
chatModel *modelModule.ChatModel,
baseDocIDs []string,
manualValueResolver ...ManualValueResolver,
) ([]string, bool) {
@@ -462,7 +464,7 @@ func ApplyMetaDataFilter(
switch method {
case "auto":
filters, err := GenMetaFilter(ctx, creds, metaData, question, nil)
filters, err := GenMetaFilter(ctx, chatModel, metaData, question, nil)
if err != nil {
common.Warn("Failed to generate meta filter", zap.Error(err))
return docIDs, false
@@ -503,7 +505,7 @@ func ApplyMetaDataFilter(
}
if len(filteredMeta) > 0 {
filters, err := GenMetaFilter(ctx, creds, filteredMeta, question, constraints)
filters, err := GenMetaFilter(ctx, chatModel, filteredMeta, question, constraints)
if err != nil {
common.Warn("Failed to generate meta filter", zap.Error(err))
return docIDs, false

View File

@@ -36,8 +36,8 @@ func parseModelName(compositeName string) (modelName, instanceName, providerName
// Format: model@instance@provider
return parts[0], parts[1], parts[2], nil
} else if len(parts) == 2 {
// Format: model@provider (legacy)
return parts[0], "", parts[1], nil
// Format: model@provider -> instance defaults to "default"
return parts[0], "default", parts[1], nil
} else if len(parts) == 1 {
return parts[0], "", "", fmt.Errorf("provider name missing in model name: %s", compositeName)
} else {
@@ -790,30 +790,6 @@ func (m *ModelProviderService) ChatToModelWithMessages(providerName, instanceNam
return nil, common.CodeServerError, errors.New("model is disabled")
}
// ChatWithMessagesToModelByApiKey sends multiple messages with roles and returns response
func (m *ModelProviderService) ChatWithMessagesToModelByApiKey(providerName, modelName, apiKey string, messages []modelModule.Message) (*string, common.ErrorCode, error) {
providerInfo := dao.GetModelProviderManager().FindProvider(providerName)
if providerInfo == nil {
return nil, common.CodeNotFound, errors.New("provider not found")
}
_, err := dao.GetModelProviderManager().GetModelByName(providerName, modelName)
if err != nil {
return nil, common.CodeNotFound, errors.New(fmt.Sprintf("provider %s model %s not found", providerName, modelName))
}
var response *modelModule.ChatResponse
response, err = providerInfo.ModelDriver.ChatWithMessages(modelName, messages, &modelModule.APIConfig{ApiKey: &apiKey}, nil)
if err != nil {
return nil, common.CodeServerError, err
}
if response == nil {
return nil, common.CodeServerError, errors.New("empty chat response")
}
return response.Answer, common.CodeSuccess, nil
}
// ChatToModelStreamWithSender streams chat response directly via sender function (best performance, no channel)
func (m *ModelProviderService) ChatToModelStreamWithSender(providerName, instanceName, modelName, userID string, messages []modelModule.Message, apiConfig *modelModule.APIConfig, modelConfig *modelModule.ChatConfig, sender func(*string, *string) error) (common.ErrorCode, error) {
// Get tenant ID from user
@@ -903,78 +879,6 @@ func (m *ModelProviderService) ChatToModelStreamWithSender(providerName, instanc
return common.CodeServerError, errors.New("model is disabled")
}
func (m *ModelProviderService) GetDefaultModel(modelType entity.ModelType, tenantID string) (*entity.ModelCredentials, error) {
// Get tenant record to find default model name
tenant, err := dao.NewTenantDAO().GetByID(tenantID)
if err != nil {
return nil, fmt.Errorf("tenant not found: %w", err)
}
// Determine model name based on model type
var defaultModelName string
switch modelType {
case entity.ModelTypeChat:
defaultModelName = tenant.LLMID
case entity.ModelTypeEmbedding:
defaultModelName = tenant.EmbdID
case entity.ModelTypeSpeech2Text:
defaultModelName = tenant.ASRID
case entity.ModelTypeImage2Text:
defaultModelName = tenant.Img2TxtID
case entity.ModelTypeRerank:
defaultModelName = tenant.RerankID
case entity.ModelTypeTTS:
if tenant.TTSID != nil {
defaultModelName = *tenant.TTSID
}
case entity.ModelTypeOCR:
return nil, errors.New("OCR model name is required")
default:
return nil, fmt.Errorf("unknown model type: %s", modelType)
}
if defaultModelName == "" {
return nil, fmt.Errorf("no default %s model is set", modelType)
}
// Look up the TenantLLM record to get provider name and API key
// Use GetByTenantIDAndLLMName which handles splitting model name and factory
tenantLLM, err := dao.NewTenantLLMDAO().GetByTenantIDAndLLMName(tenantID, defaultModelName)
if err != nil {
return nil, fmt.Errorf("failed to get tenant default model: %w", err)
}
if tenantLLM == nil {
return nil, fmt.Errorf("no default %s model found for tenant", modelType)
}
if tenantLLM.LLMName == nil || tenantLLM.APIKey == nil {
return nil, fmt.Errorf("tenant model %q has missing name or api key", defaultModelName)
}
return &entity.ModelCredentials{
ProviderName: tenantLLM.LLMFactory,
ModelName: *tenantLLM.LLMName,
APIKey: *tenantLLM.APIKey,
}, nil
}
// GetModelByName gets model credentials by model name (chat_id from search_config)
func (m *ModelProviderService) GetModelByName(modelName string, tenantID string) (*entity.ModelCredentials, error) {
tenantLLM, err := dao.NewTenantLLMDAO().GetByTenantIDAndLLMName(tenantID, modelName)
if err != nil {
return nil, fmt.Errorf("failed to get model by name: %w", err)
}
if tenantLLM == nil {
return nil, fmt.Errorf("model not found: %s", modelName)
}
return &entity.ModelCredentials{
ProviderName: tenantLLM.LLMFactory,
ModelName: *tenantLLM.LLMName,
APIKey: *tenantLLM.APIKey,
}, nil
}
// GetEmbeddingModel returns an EmbeddingModel wrapper for the given tenant
func (m *ModelProviderService) GetEmbeddingModel(tenantID, compositeModelName string) (*modelModule.EmbeddingModel, error) {
driver, modelName, apiConfig, maxTokens, err := m.getModelConfig(tenantID, compositeModelName)
@@ -1092,10 +996,6 @@ func (m *ModelProviderService) getModelConfig(tenantID, compositeModelName strin
return nil, "", nil, 0, fmt.Errorf("provider %s not found", providerName)
}
if instanceName == "" {
instanceName = "default_instance"
}
instance, err := m.modelInstanceDAO.GetByProviderIDAndInstanceName(provider.ID, instanceName)
if err != nil {
return nil, "", nil, 0, err

View File

@@ -305,6 +305,38 @@ type DefaultModelResponse struct {
Models []ModelItem `json:"models,omitempty"`
}
// GetDefaultModelName returns the full default model ID for a tenant and model type
// Format: modelName@instanceName@providerName or modelName@providerName
// Returns empty string if no default model is set
func (s *TenantService) GetDefaultModelName(tenantID string, modelType entity.ModelType) (string, error) {
tenant, err := s.tenantDAO.GetByID(tenantID)
if err != nil {
return "", err
}
var modelID string
switch modelType {
case entity.ModelTypeChat:
modelID = tenant.LLMID
case entity.ModelTypeEmbedding:
modelID = tenant.EmbdID
case entity.ModelTypeRerank:
modelID = tenant.RerankID
case entity.ModelTypeSpeech2Text:
modelID = tenant.ASRID
case entity.ModelTypeImage2Text:
modelID = tenant.Img2TxtID
case entity.ModelTypeTTS:
modelID = *tenant.TTSID
case entity.ModelTypeOCR:
modelID = tenant.OCRID
default:
return "", fmt.Errorf("invalid model type: %s", modelType)
}
return modelID, nil
}
func (s *TenantService) GetModelInfo(tenantID string, defaultModel string, modelType string) (*string, *string, *string, bool, error) {
// normally the model string is: modelName@instanceName@providerName, sometimes it's just modelName@providerName
// for the 1st case, parse defaultChatModel into three parts