diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 0e2a5fed5..9efc8cf0e 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -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 { diff --git a/internal/entity/types.go b/internal/entity/types.go index f342310ac..21ef40cae 100644 --- a/internal/entity/types.go +++ b/internal/entity/types.go @@ -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 -} diff --git a/internal/handler/providers.go b/internal/handler/providers.go index c7bab01cd..d90433cea 100644 --- a/internal/handler/providers.go +++ b/internal/handler/providers.go @@ -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) diff --git a/internal/service/chunk.go b/internal/service/chunk.go index b041ef237..c2ce08d4e 100644 --- a/internal/service/chunk.go +++ b/internal/service/chunk.go @@ -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 != "" { diff --git a/internal/service/generator.go b/internal/service/generator.go index 889f6ed87..074eab659 100644 --- a/internal/service/generator.go +++ b/internal/service/generator.go @@ -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) diff --git a/internal/service/metadata_filter.go b/internal/service/metadata_filter.go index fc76158cd..0b675defa 100644 --- a/internal/service/metadata_filter.go +++ b/internal/service/metadata_filter.go @@ -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 diff --git a/internal/service/model_service.go b/internal/service/model_service.go index 26755559d..71abefb6f 100644 --- a/internal/service/model_service.go +++ b/internal/service/model_service.go @@ -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 diff --git a/internal/service/tenant.go b/internal/service/tenant.go index e994d08c1..54606f58e 100644 --- a/internal/service/tenant.go +++ b/internal/service/tenant.go @@ -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