mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-16 12:47:19 +08:00
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:
@@ -157,6 +157,8 @@ RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \
|
||||
fi || \
|
||||
{ echo "Failed to install ODBC driver"; exit 1; }
|
||||
|
||||
|
||||
|
||||
# Add dependencies of selenium
|
||||
RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/chrome-linux64-121-0-6167-85,target=/chrome-linux64.zip \
|
||||
unzip /chrome-linux64.zip && \
|
||||
|
||||
@@ -641,9 +641,7 @@ async def update_provider_instance(tenant_id: str = None, provider_id_or_name: s
|
||||
verify = data.get("verify", True)
|
||||
|
||||
try:
|
||||
success, msg = await provider_api_service.update_provider_instance(
|
||||
tenant_id, provider_id_or_name, instance_id_or_name, instance_name, api_key, base_url, region, model_info, verify
|
||||
)
|
||||
success, msg = await provider_api_service.update_provider_instance(tenant_id, provider_id_or_name, instance_id_or_name, instance_name, api_key, base_url, region, model_info, verify)
|
||||
if success:
|
||||
return get_result(message=msg)
|
||||
else:
|
||||
@@ -961,13 +959,12 @@ async def delete_models_from_instance(tenant_id: str, provider_id_or_name: str,
|
||||
return get_error_data_result(message="Internal server error")
|
||||
|
||||
|
||||
|
||||
@manager.route("/providers/<provider_id_or_name>/instances/<instance_id_or_name>/models/<path:model_name>", methods=["PATCH"]) # noqa: F821
|
||||
@login_required
|
||||
@add_tenant_id_to_kwargs
|
||||
async def alter_model(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None, model_name: str = None):
|
||||
"""
|
||||
Enable or disable a model, or update max_tokens
|
||||
Alter a model's status, max_tokens, model_type, or extra fields.
|
||||
---
|
||||
tags:
|
||||
- Providers
|
||||
@@ -996,35 +993,40 @@ async def alter_model(tenant_id: str = None, provider_id_or_name: str = None, in
|
||||
description: Bearer token for authentication.
|
||||
- in: body
|
||||
name: body
|
||||
description: Model status update.
|
||||
description: Model update parameters.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: ["active", "inactive"]
|
||||
description: Model status.
|
||||
max_tokens:
|
||||
type: integer
|
||||
description: Maximum number of tokens.
|
||||
model_type:
|
||||
type: string
|
||||
description: Model type (e.g. "chat", "embedding").
|
||||
extra:
|
||||
type: object
|
||||
description: Additional model configuration.
|
||||
responses:
|
||||
200:
|
||||
description: Model status updated.
|
||||
description: Model updated.
|
||||
schema:
|
||||
type: object
|
||||
"""
|
||||
data = await request.get_json()
|
||||
if not data or ("status" not in data and "max_tokens" not in data):
|
||||
return get_error_argument_result(message="status or max_tokens required.")
|
||||
if not data or not any(k in data for k in ("status", "max_tokens", "model_type", "extra")):
|
||||
return get_error_argument_result(message="At least one of status, max_tokens, model_type, or extra is required.")
|
||||
|
||||
update_dict = {k: data[k] for k in ["status", "max_tokens", "model_type", "extra"] if k in data}
|
||||
if update_dict.get("status") and update_dict["status"] not in ("active", "inactive"):
|
||||
return get_error_argument_result(message="status must be 'active' or 'inactive'")
|
||||
|
||||
try:
|
||||
success, msg = provider_api_service.update_model(
|
||||
tenant_id, provider_id_or_name, instance_id_or_name, model_name, update_dict
|
||||
)
|
||||
success, msg = provider_api_service.update_model(tenant_id, provider_id_or_name, instance_id_or_name, model_name, update_dict)
|
||||
if success:
|
||||
return get_result(message=msg)
|
||||
else:
|
||||
|
||||
@@ -28,7 +28,7 @@ _LEGACY_MODEL_TYPE_ALIASES = {
|
||||
}
|
||||
|
||||
|
||||
def calculate_model_type(model_type_name_list: List[str]|str) -> int:
|
||||
def calculate_model_type(model_type_name_list: List[str] | str) -> int:
|
||||
model_type = 0
|
||||
if isinstance(model_type_name_list, str):
|
||||
model_type_name_list = [model_type_name_list]
|
||||
|
||||
@@ -94,7 +94,7 @@ class LLMType(StrEnum):
|
||||
|
||||
|
||||
class ModelTypeBinary(Enum):
|
||||
CHAT = 0b0000001 # 1 << 0 = 1
|
||||
CHAT = 0b0000001 # 1 << 0 = 1
|
||||
EMBEDDING = 0b0000010 # 1 << 1 = 2
|
||||
ASR = 0b0000100 # 1 << 2 = 4
|
||||
VISION = 0b0001000 # 1 << 3 = 8
|
||||
|
||||
@@ -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{},
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -241,4 +241,5 @@ def test_openai_compatible_reference_metadata_fields_filter_accepts_array(rest_c
|
||||
choice_msg = payload["choices"][0]["message"]
|
||||
skip_if_go_proxy_upstream_error(choice_msg)
|
||||
assert "reference" in choice_msg, payload
|
||||
print(payload)
|
||||
assert isinstance(choice_msg["reference"], list), payload
|
||||
|
||||
@@ -27,14 +27,14 @@ import pytest
|
||||
pytestmark = pytest.mark.p2
|
||||
|
||||
|
||||
class _StubModelTypeBinary(IntEnum):
|
||||
CHAT = 1
|
||||
EMBEDDING = 2
|
||||
ASR = 4
|
||||
VISION = 8
|
||||
RERANK = 16
|
||||
TTS = 32
|
||||
OCR = 64
|
||||
class _StubModelTypeBinary(IntEnum):
|
||||
CHAT = 1
|
||||
EMBEDDING = 2
|
||||
ASR = 4
|
||||
VISION = 8
|
||||
RERANK = 16
|
||||
TTS = 32
|
||||
OCR = 64
|
||||
|
||||
|
||||
def _stub(monkeypatch, name, **attrs):
|
||||
|
||||
Reference in New Issue
Block a user