Fix: align go provider apis with python apis (#16867)

### Summary

As title.

---------

Co-authored-by: Jin Hai <haijin.chn@gmail.com>
Co-authored-by: Wang Qi <wangq8@outlook.com>
This commit is contained in:
Lynn
2026-07-14 21:06:11 +08:00
committed by GitHub
parent 55d5879622
commit bd7a3bb878
18 changed files with 790 additions and 577 deletions

View File

@@ -199,46 +199,6 @@ func TestSplitCompositeLLMID(t *testing.T) {
}
}
func TestCategorize_ResolvesTenantLLMCredentials(t *testing.T) {
db := setupComponentTestDB(t)
pushComponentDB(t, db)
apiKey := "tenant-llm-key"
apiBase := "https://tenant-llm.example"
modelName := "Qwen/Qwen3-8B"
if err := db.Create(&entity.TenantLLM{
TenantID: "tenant-1",
LLMFactory: "SILICONFLOW",
LLMName: &modelName,
APIKey: &apiKey,
APIBase: &apiBase,
Status: "1",
}).Error; err != nil {
t.Fatalf("create tenant_llm: %v", err)
}
stub := &stubInvoker{resp: &ChatInvokeResponse{Content: "support", Model: "stub"}}
withStubInvoker(t, stub)
c := NewCategorizeComponent(CategorizeParam{
ModelID: "Qwen/Qwen3-8B@default@SILICONFLOW",
Categories: []string{"sales", "support"},
DefaultCategory: "support",
})
_, err := c.Invoke(stateWithTenant("tenant-1"), map[string]any{})
if err != nil {
t.Fatalf("Invoke: %v", err)
}
if stub.captured == nil {
t.Fatal("invoker not called")
}
if stub.captured.APIKey != apiKey {
t.Fatalf("APIKey=%q, want %q", stub.captured.APIKey, apiKey)
}
if stub.captured.BaseURL != apiBase {
t.Fatalf("BaseURL=%q, want %q", stub.captured.BaseURL, apiBase)
}
}
func TestCategorize_ResolvesTenantModelInstanceCredentials(t *testing.T) {
db := setupComponentTestDB(t)
pushComponentDB(t, db)
@@ -387,7 +347,6 @@ func setupComponentTestDB(t *testing.T) *gorm.DB {
t.Fatalf("open sqlite: %v", err)
}
if err := db.AutoMigrate(
&entity.TenantLLM{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},

View File

@@ -733,6 +733,12 @@ int32_t RAGAnalyzer::Load() {
int32_t freq = std::stoi(results[1]);
freq = int32_t(std::log(float(freq) / DENOMINATOR) + 0.5);
int32_t pos_idx = pos_table_->GetPOSIndex(results[2]);
// If the POS tag is not in the POS table (e.g. "eng" for
// English words), default to index 0. Using -1 would cause
// Encode() to produce a negative int32_t which the darts
// trie rejects.
if (pos_idx < 0)
pos_idx = 0;
int value = Encode(freq, pos_idx);
trie_->Add(results[0], value);
std::string rkey = RKey(results[0]);

View File

@@ -169,6 +169,7 @@ type Model struct {
MaxDimension *int `json:"max_dimension"` // used by embedding models
Dimensions []int `json:"dimensions"`
Alias []string `json:"alias"`
Rank *int `json:"rank"`
ModelTypeMap map[string]bool
}
@@ -488,7 +489,7 @@ func (pm *ProviderManager) ListModels(providerName string) ([]map[string]interfa
modelData := map[string]interface{}{
"name": model.Name,
"max_tokens": model.MaxTokens,
"model_type": model.ModelTypes,
"model_types": model.ModelTypes,
"max_dimension": model.MaxDimension,
"dimensions": model.Dimensions,
}
@@ -513,7 +514,7 @@ func (pm *ProviderManager) GetModelByName(providerName, modelName string) (*Mode
if provider == nil {
return nil, fmt.Errorf("provider '%s' not found", providerName)
}
model := pm.findModel(provider, modelName)
model := pm.FindModel(provider, modelName)
if model == nil {
return nil, fmt.Errorf("model '%s' not found", modelName)
}
@@ -525,7 +526,7 @@ func (pm *ProviderManager) GetModelUrl(providerName, modelName, modelType string
if provider == nil {
return nil, nil, fmt.Errorf("provider '%s' not found", providerName)
}
model := pm.findModel(provider, modelName)
model := pm.FindModel(provider, modelName)
if model == nil {
return nil, nil, fmt.Errorf("model '%s' not found", modelName)
}
@@ -568,7 +569,7 @@ func (pm *ProviderManager) SearchModelInfo(providerName, modelName string, filte
return resp
}
model := pm.findModel(provider, modelName)
model := pm.FindModel(provider, modelName)
if model == nil {
resp.Code = 404
resp.Message = fmt.Sprintf("Model '%s' not found in provider '%s'", modelName, providerName)
@@ -794,7 +795,7 @@ func (pm *ProviderManager) FindProvider(name string) *Provider {
}
// Helper: Find model by name
func (pm *ProviderManager) findModel(provider *Provider, modelName string) *Model {
func (pm *ProviderManager) FindModel(provider *Provider, modelName string) *Model {
for i := range provider.Models {
if strings.EqualFold(provider.Models[i].Name, modelName) {
return provider.Models[i]

View File

@@ -244,16 +244,15 @@ func (h *ProviderHandler) ShowProviderInstance(c *gin.Context) {
return
}
instanceName := c.Param("instance_name")
if instanceName == "" {
instanceIDOrName := c.Param("instance_name")
if instanceIDOrName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, "Instance name is required")
return
}
userID := c.GetString("user_id")
// Get tenant ID from user
instance, errorCode, err := h.modelProviderService.ShowProviderInstance(providerName, instanceName, userID)
instance, errorCode, err := h.modelProviderService.ShowProviderInstance(providerName, instanceIDOrName, userID)
if err != nil {
common.ErrorWithCode(c, errorCode, err.Error())
return
@@ -516,12 +515,15 @@ func (h *ProviderHandler) ListInstanceModels(c *gin.Context) {
common.SuccessWithData(c, modelInstances, "success")
}
type EnableOrDisableModelRequest struct {
ModelID string `json:"model_id"`
Status string `json:"status"`
type AlterModelRequest struct {
ModelID string `json:"model_id"`
Status string `json:"status"`
MaxTokens int `json:"max_tokens"`
ModelType interface{} `json:"model_type"`
Extra interface{} `json:"extra"`
}
func (h *ProviderHandler) EnableOrDisableModel(c *gin.Context) {
func (h *ProviderHandler) AlterModel(c *gin.Context) {
providerName := c.Param("provider_name")
if providerName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, 400, nil, "Provider name is required")
@@ -534,29 +536,43 @@ func (h *ProviderHandler) EnableOrDisableModel(c *gin.Context) {
return
}
var req EnableOrDisableModelRequest
var req AlterModelRequest
if err := c.ShouldBindJSON(&req); err != nil {
println("JSON bind error: %v (type: %T)", err, err)
common.ErrorWithCode(c, common.CodeBadRequest, err.Error())
return
}
userID := c.GetString("user_id")
modelID := strings.TrimSpace(req.ModelID)
modelName := strings.TrimPrefix(c.Param("model_name"), "/")
modelName = strings.TrimSpace(modelName)
modelID := strings.TrimSpace(req.ModelID)
if modelName == "" && modelID == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "model_name or model_id is required")
return
}
status := strings.TrimSpace(req.Status)
if status != "active" && status != "inactive" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "Status must be active or inactive")
if status != "" && status != "active" && status != "inactive" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "status must be 'active' or 'inactive'")
return
}
code, err := h.modelProviderService.UpdateModelStatus(providerName, instanceName, modelName, userID, modelID, status)
updateDict := make(map[string]interface{})
if status != "" {
updateDict["status"] = status
}
if req.MaxTokens > 0 {
updateDict["max_tokens"] = req.MaxTokens
}
if req.ModelType != nil {
updateDict["model_type"] = req.ModelType
}
if req.Extra != nil {
updateDict["extra"] = req.Extra
}
code, err := h.modelProviderService.AlterModel(providerName, instanceName, modelName, userID, modelID, updateDict)
if err != nil {
common.ErrorWithCode(c, code, err.Error())
return
@@ -585,30 +601,6 @@ func prepareProviderInstance(providerName, instanceName, reqProviderName, reqIns
return nil
}
func prepareAddModelRequest(req *service.AddModelRequest, providerName, instanceName string) error {
if err := prepareProviderInstance(providerName, instanceName, req.ProviderName, req.InstanceName); err != nil {
return err
}
if len(req.Models) == 0 {
return errors.New("Models are required")
}
for _, model := range req.Models {
if model.ModelName == "" {
return errors.New("Model name is required")
}
if len(model.ModelTypes) == 0 {
return errors.New("Model type is required")
}
}
req.ProviderName = providerName
req.InstanceName = instanceName
return nil
}
func (h *ProviderHandler) AddModel(c *gin.Context) {
var req service.AddModelRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -617,8 +609,21 @@ func (h *ProviderHandler) AddModel(c *gin.Context) {
return
}
if err := prepareAddModelRequest(&req, c.Param("provider_name"), c.Param("instance_name")); err != nil {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeDataError, nil, err.Error())
req.ProviderName = c.Param("provider_name")
req.InstanceName = c.Param("instance_name")
if req.ProviderName == "" || req.InstanceName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "provider_name and instance_name are required")
return
}
if req.ModelName == "" {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "model_name is required")
return
}
if len(req.ModelTypes) == 0 {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "model_type is required")
return
}
@@ -634,8 +639,7 @@ func (h *ProviderHandler) AddModel(c *gin.Context) {
}
type DropInstanceModelRequest struct {
ModelIDs []string `json:"model_ids"`
Models []string `json:"models"`
ModelNames []string `json:"model_name"`
}
func (h *ProviderHandler) DropInstanceModels(c *gin.Context) {
@@ -655,14 +659,14 @@ func (h *ProviderHandler) DropInstanceModels(c *gin.Context) {
common.ErrorWithCode(c, common.CodeBadRequest, err.Error())
return
}
if len(req.ModelIDs) == 0 && len(req.Models) == 0 {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "model_ids or models is required")
if len(req.ModelNames) == 0 {
common.ResponseWithHttpCodeData(c, http.StatusBadRequest, common.CodeBadRequest, nil, "model_name is required")
return
}
userID := c.GetString("user_id")
code, err := h.modelProviderService.DropInstanceModels(providerName, instanceName, userID, req.ModelIDs, req.Models)
code, err := h.modelProviderService.DropInstanceModels(providerName, instanceName, userID, req.ModelNames)
if err != nil {
common.ErrorWithCode(c, code, err.Error())
return

View File

@@ -84,7 +84,7 @@ func decodeProviderHandlerResponse(t *testing.T, recorder *httptest.ResponseReco
return body
}
func TestProviderHandlerEnableOrDisableModelRejectsMissingModelSelector(t *testing.T) {
func TestProviderHandlerAlterModelRejectsMissingModelSelector(t *testing.T) {
ctx, recorder := newProviderHandlerRequest(
t,
map[string]interface{}{"status": "active"},
@@ -92,7 +92,7 @@ func TestProviderHandlerEnableOrDisableModelRejectsMissingModelSelector(t *testi
gin.Param{Key: "instance_name", Value: "default"},
)
NewProviderHandler(nil, service.NewModelProviderService()).EnableOrDisableModel(ctx)
NewProviderHandler(nil, service.NewModelProviderService()).AlterModel(ctx)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusBadRequest, recorder.Body.String())
@@ -103,7 +103,7 @@ func TestProviderHandlerEnableOrDisableModelRejectsMissingModelSelector(t *testi
}
}
func TestProviderHandlerEnableOrDisableModelRejectsInvalidStatus(t *testing.T) {
func TestProviderHandlerAlterModelRejectsInvalidStatus(t *testing.T) {
ctx, recorder := newProviderHandlerRequest(
t,
map[string]interface{}{"status": "disabled"},
@@ -112,7 +112,7 @@ func TestProviderHandlerEnableOrDisableModelRejectsInvalidStatus(t *testing.T) {
gin.Param{Key: "model_name", Value: "gpt-test"},
)
NewProviderHandler(nil, service.NewModelProviderService()).EnableOrDisableModel(ctx)
NewProviderHandler(nil, service.NewModelProviderService()).AlterModel(ctx)
if recorder.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusBadRequest, recorder.Body.String())
@@ -123,7 +123,7 @@ func TestProviderHandlerEnableOrDisableModelRejectsInvalidStatus(t *testing.T) {
}
}
func TestProviderHandlerEnableOrDisableModelUpdatesStatus(t *testing.T) {
func TestProviderHandlerAlterModelUpdatesStatus(t *testing.T) {
db := setupProviderHandlerTestDB(t)
useProviderHandlerTestDB(t, db)
seedProviderHandlerModel(t, db)
@@ -136,7 +136,7 @@ func TestProviderHandlerEnableOrDisableModelUpdatesStatus(t *testing.T) {
gin.Param{Key: "model_name", Value: "gpt-test"},
)
NewProviderHandler(nil, service.NewModelProviderService()).EnableOrDisableModel(ctx)
NewProviderHandler(nil, service.NewModelProviderService()).AlterModel(ctx)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d; body=%s", recorder.Code, http.StatusOK, recorder.Body.String())

View File

@@ -539,7 +539,7 @@ func (r *Router) Setup(engine *gin.Engine) {
provider.PUT("/:provider_name/instances/:instance_name", r.providerHandler.AlterProviderInstance)
provider.DELETE("/:provider_name/instances", r.providerHandler.DropProviderInstance)
provider.GET("/:provider_name/instances/:instance_name/models", r.providerHandler.ListInstanceModels)
provider.PATCH("/:provider_name/instances/:instance_name/models/*model_name", r.providerHandler.EnableOrDisableModel)
provider.PATCH("/:provider_name/instances/:instance_name/models/*model_name", r.providerHandler.AlterModel)
provider.POST("/:provider_name/instances/:instance_name/models", r.providerHandler.AddModel)
provider.DELETE("/:provider_name/instances/:instance_name/models", r.providerHandler.DropInstanceModels)
v1.POST("/chat/to_model", r.providerHandler.ChatToModel)

View File

@@ -158,6 +158,11 @@ func TestRunAgent_RealCanvas_BeginMessage(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -240,6 +245,11 @@ func TestRunAgent_RealCanvas_WaitForUserResume(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -367,6 +377,11 @@ func TestRunAgent_RealCanvas_WaitForUserResume_EventSemantics(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -466,6 +481,11 @@ func TestRunAgent_RealCanvas_GroupedParallelOuterFollower(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -594,6 +614,11 @@ func TestRunAgent_AllFixture_LoopInterruptResume(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -698,6 +723,11 @@ func TestRunAgent_AllFixture_LoopInterruptResume_MultiTurn(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -786,6 +816,11 @@ func TestRunAgent_AllFixture_IterationFormatsItems(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -882,6 +917,11 @@ func TestRunAgent_AllFixture_VarAssigner(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -942,6 +982,11 @@ func TestRunAgent_AllFixture_DataOps(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -1018,6 +1063,11 @@ func TestRunAgent_RealCanvas_CompileFails(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -1090,6 +1140,11 @@ func TestRunAgent_AllFixture_CategorizeResume(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -1217,6 +1272,11 @@ func TestRunAgent_RealCanvas_InvokeFails(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}
@@ -1319,6 +1379,11 @@ func TestRunAgent_RunTracker_AttachCheckpoint_CallSequence(t *testing.T) {
&entity.UserTenant{},
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.APIToken{},
&entity.API4Conversation{},
&entity.TenantModelProvider{},
&entity.TenantModelInstance{},
&entity.TenantModel{},
); err != nil {
t.Fatalf("migrate: %v", err)
}

View File

@@ -220,7 +220,9 @@ func TestGetVersion_WrongCanvas(t *testing.T) {
t.Helper()
if err := testDB.AutoMigrate(
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.UserTenant{},
); err != nil {
t.Fatalf("failed to migrate: %v", err)
}
@@ -248,7 +250,9 @@ func TestGetVersion_NotFound(t *testing.T) {
t.Helper()
if err := testDB.AutoMigrate(
&entity.UserCanvas{},
&entity.UserCanvasVersion{},
&entity.UserTenant{},
); err != nil {
t.Fatalf("failed to migrate: %v", err)
}

File diff suppressed because it is too large Load Diff

View File

@@ -155,14 +155,14 @@ func seedModelProviderServiceScope(t *testing.T, db *gorm.DB) {
}
}
func TestModelProviderServiceUpdateModelStatusByID(t *testing.T) {
func TestModelProviderServiceAlterModelStatusByID(t *testing.T) {
db := setupModelProviderServiceTestDB(t)
useModelProviderServiceTestDB(t, db)
seedModelProviderServiceScope(t, db)
code, err := NewModelProviderService().UpdateModelStatus("OpenAI", "default", "", "user-1", "model-1", "inactive")
code, err := NewModelProviderService().AlterModel("OpenAI", "default", "", "user-1", "model-1", map[string]interface{}{"status": "inactive"})
if err != nil {
t.Fatalf("UpdateModelStatus() error = %v", err)
t.Fatalf("AlterModel() error = %v", err)
}
if code != common.CodeSuccess {
t.Fatalf("code = %v, want %v", code, common.CodeSuccess)
@@ -197,23 +197,23 @@ func TestModelProviderServiceGetModelConfigByID(t *testing.T) {
}
}
func TestModelProviderServiceUpdateModelStatusRejectsInvalidStatus(t *testing.T) {
code, err := NewModelProviderService().UpdateModelStatus("OpenAI", "default", "", "user-1", "model-1", "disabled")
func TestModelProviderServiceAlterModelRejectsInvalidStatus(t *testing.T) {
code, err := NewModelProviderService().AlterModel("OpenAI", "default", "", "user-1", "model-1", map[string]interface{}{"status": "disabled"})
if err == nil {
t.Fatalf("UpdateModelStatus() error = nil, want invalid status error")
t.Fatalf("AlterModel() error = nil, want invalid status error")
}
if code != common.CodeBadRequest {
t.Fatalf("code = %v, want %v", code, common.CodeBadRequest)
}
if !strings.Contains(err.Error(), "status must be active or inactive") {
if !strings.Contains(err.Error(), "status must be") {
t.Fatalf("error = %v, want status validation message", err)
}
}
func TestModelProviderServiceUpdateModelStatusRejectsMissingModelSelector(t *testing.T) {
code, err := NewModelProviderService().UpdateModelStatus("OpenAI", "default", "", "user-1", "", "active")
func TestModelProviderServiceAlterModelRejectsMissingModelSelector(t *testing.T) {
code, err := NewModelProviderService().AlterModel("OpenAI", "default", "", "user-1", "", map[string]interface{}{"status": "active"})
if err == nil {
t.Fatalf("UpdateModelStatus() error = nil, want missing model selector error")
t.Fatalf("AlterModel() error = nil, want missing model selector error")
}
if code != common.CodeBadRequest {
t.Fatalf("code = %v, want %v", code, common.CodeBadRequest)
@@ -223,7 +223,7 @@ func TestModelProviderServiceUpdateModelStatusRejectsMissingModelSelector(t *tes
}
}
func TestModelProviderServiceUpdateModelStatusRejectsWrongScopedModelID(t *testing.T) {
func TestModelProviderServiceAlterModelRejectsWrongScopedModelID(t *testing.T) {
db := setupModelProviderServiceTestDB(t)
useModelProviderServiceTestDB(t, db)
seedModelProviderServiceScope(t, db)
@@ -231,9 +231,9 @@ func TestModelProviderServiceUpdateModelStatusRejectsWrongScopedModelID(t *testi
t.Fatalf("failed to seed second instance: %v", err)
}
code, err := NewModelProviderService().UpdateModelStatus("OpenAI", "other", "", "user-1", "model-1", "inactive")
code, err := NewModelProviderService().AlterModel("OpenAI", "other", "", "user-1", "model-1", map[string]interface{}{"status": "inactive"})
if err == nil {
t.Fatalf("UpdateModelStatus() error = nil, want not found error")
t.Fatalf("AlterModel() error = nil, want not found error")
}
if code != common.CodeNotFound {
t.Fatalf("code = %v, want %v", code, common.CodeNotFound)

View File

@@ -358,6 +358,7 @@ func (s *OpenAIChatService) OpenAIChatCompletions(c *gin.Context, userID, chatID
if result.Final {
finalContent := strings.TrimSpace(result.Answer)
fullContent = finalContent
finalReference = []FormattedChunk{}
if ref, ok := result.Reference["chunks"]; ok {
if chunks, ok := ref.([]map[string]interface{}); ok {
finalReference = formatChunks(chunks)
@@ -397,6 +398,7 @@ func (s *OpenAIChatService) OpenAIChatCompletions(c *gin.Context, userID, chatID
}
if finalReference == nil && openaiReq.NeedReference {
finalReference = []FormattedChunk{}
if ref, ok := lastResult.Reference["chunks"]; ok {
if chunks, ok := ref.([]map[string]interface{}); ok {
finalReference = formatChunks(chunks)
@@ -441,6 +443,7 @@ func (s *OpenAIChatService) OpenAIChatCompletions(c *gin.Context, userID, chatID
TotalTokens: promptTokens + completionTokens,
}
if openaiReq.NeedReference {
resp.Reference = []FormattedChunk{}
if ref, ok := finalResult.Reference["chunks"]; ok {
if chunks, ok := ref.([]map[string]interface{}); ok {
resp.Reference = formatChunks(chunks)

View File

@@ -489,71 +489,103 @@ func (s *TenantService) GetDefaultModelName(tenantID string, modelType entity.Mo
}
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
defaultChatModelParts := strings.Split(defaultModel, "@")
var providerName *string
var instanceName *string
var modelName *string
if len(defaultChatModelParts) >= 3 {
providerName = &defaultChatModelParts[len(defaultChatModelParts)-1]
instanceName = &defaultChatModelParts[len(defaultChatModelParts)-2]
joinedModelName := strings.Join(defaultChatModelParts[:len(defaultChatModelParts)-2], "@")
modelName = &joinedModelName
} else if len(defaultChatModelParts) == 2 {
providerName = &defaultChatModelParts[1]
instanceName = new(string)
*instanceName = "default"
modelName = &defaultChatModelParts[0]
} else {
return nil, nil, nil, false, fmt.Errorf("invalid model string: %s", defaultModel)
// Mirror Python's _get_model_info: right-anchored rsplit so that model
// names containing '@' (e.g. LM Studio IDs like
// "text-embedding-nomic-embed-text-v1.5@q8_0") remain intact.
// The composite key is: modelName@instanceName@providerName or
// modelName@providerName.
parts := rsplitN(defaultModel, "@", 2)
var modelName, instanceName, providerName string
switch len(parts) {
case 3:
modelName, instanceName, providerName = parts[0], parts[1], parts[2]
case 2:
modelName, providerName = parts[0], parts[1]
instanceName = "default"
default:
modelName = parts[0]
providerName = ""
instanceName = "default"
}
if modelType == "ocr" {
if *providerName == "infiniflow" && *instanceName == "default" && *modelName == "deepdoc" {
return providerName, instanceName, modelName, true, nil
}
// Special case: OCR with infiniflow@default@deepdoc is always enabled.
if modelType == "ocr" && providerName == "infiniflow" && instanceName == "default" && modelName == "deepdoc" {
return &providerName, &instanceName, &modelName, true, nil
}
// Check if the provider and instance exists
modelProvider, err := s.modelProviderDAO.GetByTenantIDAndProviderName(tenantID, *providerName)
// Special case: TEI Builtin embedding model.
composeProfiles := common.GetEnv(common.EnvComposeProfiles)
teiModel := common.GetEnv(common.EnvTEIModel)
if modelType == "embedding" && strings.Contains(composeProfiles, "tei-") && teiModel != "" &&
modelName == teiModel && (providerName == "" || providerName == "Builtin") {
return &providerName, &instanceName, &modelName, true, nil
}
// Check if the provider exists for the tenant.
modelProvider, err := s.modelProviderDAO.GetByTenantIDAndProviderName(tenantID, providerName)
if err != nil {
return nil, nil, nil, false, err
}
modelInstance, err := s.modelInstanceDAO.GetByProviderIDAndInstanceName(modelProvider.ID, *instanceName)
// Check if the instance exists.
modelInstance, err := s.modelInstanceDAO.GetByProviderIDAndInstanceName(modelProvider.ID, instanceName)
if err != nil {
return nil, nil, nil, false, err
}
modelSchema, err := dao.GetModelProviderManager().GetModelByName(*providerName, *modelName)
// Validate that the factory model supports this model type.
modelSchema, err := dao.GetModelProviderManager().GetModelByName(providerName, modelName)
if err != nil {
return nil, nil, nil, false, err
}
factoryModelType := factoryModelTypeName(modelType)
if !modelSchema.ModelTypeMap[factoryModelType] {
return nil, nil, nil, false, fmt.Errorf("model %s isn't a %s model", *modelName, modelType)
return nil, nil, nil, false, fmt.Errorf("model %s isn't a %s model", modelName, modelType)
}
var modelEntity *entity.TenantModel
modelEntity, err = s.modelDAO.GetModelByProviderIDAndInstanceIDAndModelName(modelProvider.ID, modelInstance.ID, *modelName)
// Check if the model exists and is active.
modelEntity, err := s.modelDAO.GetModelByProviderIDAndInstanceIDAndModelName(modelProvider.ID, modelInstance.ID, modelName)
if err != nil {
errString := err.Error()
if !strings.Contains(errString, "record not found") {
if !dao.IsNotFoundErr(err) {
return nil, nil, nil, false, err
}
}
if modelEntity == nil {
return nil, nil, nil, false, fmt.Errorf("model %s isn't available", *modelName)
return nil, nil, nil, false, fmt.Errorf("model %s isn't available", modelName)
}
if modelEntity.Status != "active" {
return nil, nil, nil, false, fmt.Errorf("model %s isn't available", *modelName)
return nil, nil, nil, false, fmt.Errorf("model %s isn't available", modelName)
}
return providerName, instanceName, modelName, true, nil
return &providerName, &instanceName, &modelName, true, nil
}
// rsplitN splits s by sep from the right, limiting to n+1 parts (mirrors
// Python's str.rsplit(sep, maxsplit)).
func rsplitN(s, sep string, n int) []string {
if n <= 0 {
return []string{s}
}
result := make([]string, 0, n+1)
remaining := s
for i := 0; i < n; i++ {
idx := strings.LastIndex(remaining, sep)
if idx < 0 {
result = append(result, remaining)
// Reverse the collected parts.
for left, right := 0, len(result)-1; left < right; left, right = left+1, right-1 {
result[left], result[right] = result[right], result[left]
}
return result
}
result = append(result, remaining[idx+len(sep):])
remaining = remaining[:idx]
}
result = append(result, remaining)
for left, right := 0, len(result)-1; left < right; left, right = left+1, right-1 {
result[left], result[right] = result[right], result[left]
}
return result
}
func (s *TenantService) ListTenantDefaultModels(userID string) ([]ModelItem, error) {