+18


![dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)

![autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)




FFXN
GitHub
yyh
盐粒 Yanli
autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Tianle
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Yunlu Wen
zyssyz123
Claude Opus 4.7
chariri
Asuka Minato
Copilot Autofix powered by AI
Nian
非法操作
Carmen Fernández Ruiz
wangxiaolei
QuantumGhost
L1nSn0w
Evan
Escape0707
Jingyi
Amr Sherif
ZHOU ZHICHEN
unknown
JzoNg
Xiyuan Chen
-LAN-
107bba0116
Signed-off-by: dependabot[bot] <[email protected]> Signed-off-by: EvanYao826 <[email protected]> Co-authored-by: yyh <[email protected]> Co-authored-by: 盐粒 Yanli <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Tianle <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yunlu Wen <[email protected]> Co-authored-by: zyssyz123 <[email protected]> Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]> Co-authored-by: chariri <[email protected]> Co-authored-by: Asuka Minato <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> Co-authored-by: Nian <[email protected]> Co-authored-by: 非法操作 <[email protected]> Co-authored-by: Carmen Fernández Ruiz <[email protected]> Co-authored-by: wangxiaolei <[email protected]> Co-authored-by: QuantumGhost <[email protected]> Co-authored-by: L1nSn0w <[email protected]> Co-authored-by: Evan <[email protected]> Co-authored-by: Escape0707 <[email protected]> Co-authored-by: Jingyi <[email protected]> Co-authored-by: Amr Sherif <[email protected]> Co-authored-by: ZHOU ZHICHEN <[email protected]> Co-authored-by: unknown <[email protected]> Co-authored-by: JzoNg <[email protected]> Co-authored-by: Xiyuan Chen <[email protected]> Co-authored-by: -LAN- <[email protected]>
538 lines
18 KiB
Python
538 lines
18 KiB
Python
import logging
|
|
from typing import Any, cast
|
|
|
|
from flask import request
|
|
from flask_restx import Resource
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
from controllers.common.fields import SimpleResultResponse
|
|
from controllers.common.schema import register_enum_models, register_response_schema_models, register_schema_models
|
|
from controllers.console import console_ns
|
|
from controllers.console.wraps import (
|
|
account_initialization_required,
|
|
is_admin_or_owner_required,
|
|
setup_required,
|
|
with_current_tenant_id,
|
|
)
|
|
from graphon.model_runtime.entities.model_entities import ModelType
|
|
from graphon.model_runtime.errors.validate import CredentialsValidateFailedError
|
|
from graphon.model_runtime.utils.encoders import jsonable_encoder
|
|
from libs.helper import uuid_value
|
|
from libs.login import login_required
|
|
from services.model_load_balancing_service import ModelLoadBalancingService
|
|
from services.model_provider_service import ModelProviderService
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ParserGetDefault(BaseModel):
|
|
model_type: ModelType
|
|
|
|
|
|
class Inner(BaseModel):
|
|
model_type: ModelType
|
|
model: str | None = None
|
|
provider: str | None = None
|
|
|
|
|
|
class ParserPostDefault(BaseModel):
|
|
model_settings: list[Inner]
|
|
|
|
|
|
class ParserDeleteModels(BaseModel):
|
|
model: str
|
|
model_type: ModelType
|
|
|
|
|
|
class LoadBalancingPayload(BaseModel):
|
|
configs: list[dict[str, Any]] | None = None
|
|
enabled: bool | None = None
|
|
|
|
|
|
class ParserPostModels(BaseModel):
|
|
model: str
|
|
model_type: ModelType
|
|
load_balancing: LoadBalancingPayload | None = None
|
|
config_from: str | None = None
|
|
credential_id: str | None = None
|
|
|
|
@field_validator("credential_id")
|
|
@classmethod
|
|
def validate_credential_id(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return value
|
|
return uuid_value(value)
|
|
|
|
|
|
class ParserGetCredentials(BaseModel):
|
|
model: str
|
|
model_type: ModelType
|
|
config_from: str | None = None
|
|
credential_id: str | None = None
|
|
|
|
@field_validator("credential_id")
|
|
@classmethod
|
|
def validate_get_credential_id(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return value
|
|
return uuid_value(value)
|
|
|
|
|
|
class ParserCredentialBase(BaseModel):
|
|
model: str
|
|
model_type: ModelType
|
|
|
|
|
|
class ParserCreateCredential(ParserCredentialBase):
|
|
name: str | None = Field(default=None, max_length=30)
|
|
credentials: dict[str, Any]
|
|
|
|
|
|
class ParserUpdateCredential(ParserCredentialBase):
|
|
credential_id: str
|
|
credentials: dict[str, Any]
|
|
name: str | None = Field(default=None, max_length=30)
|
|
|
|
@field_validator("credential_id")
|
|
@classmethod
|
|
def validate_update_credential_id(cls, value: str) -> str:
|
|
return uuid_value(value)
|
|
|
|
|
|
class ParserDeleteCredential(ParserCredentialBase):
|
|
credential_id: str
|
|
|
|
@field_validator("credential_id")
|
|
@classmethod
|
|
def validate_delete_credential_id(cls, value: str) -> str:
|
|
return uuid_value(value)
|
|
|
|
|
|
class ParserParameter(BaseModel):
|
|
model: str
|
|
|
|
|
|
class ParserSwitch(BaseModel):
|
|
model: str
|
|
model_type: ModelType
|
|
credential_id: str
|
|
|
|
|
|
register_schema_models(
|
|
console_ns,
|
|
ParserGetDefault,
|
|
ParserPostDefault,
|
|
ParserDeleteModels,
|
|
ParserPostModels,
|
|
ParserGetCredentials,
|
|
ParserCreateCredential,
|
|
ParserUpdateCredential,
|
|
ParserDeleteCredential,
|
|
ParserParameter,
|
|
Inner,
|
|
ParserSwitch,
|
|
)
|
|
register_response_schema_models(console_ns, SimpleResultResponse)
|
|
|
|
register_enum_models(console_ns, ModelType)
|
|
|
|
|
|
@console_ns.route("/workspaces/current/default-model")
|
|
class DefaultModelApi(Resource):
|
|
@console_ns.expect(console_ns.models[ParserGetDefault.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def get(self, tenant_id: str):
|
|
args = ParserGetDefault.model_validate(request.args.to_dict(flat=True))
|
|
|
|
model_provider_service = ModelProviderService()
|
|
default_model_entity = model_provider_service.get_default_model_of_model_type(
|
|
tenant_id=tenant_id, model_type=args.model_type
|
|
)
|
|
|
|
return jsonable_encoder({"data": default_model_entity})
|
|
|
|
@console_ns.expect(console_ns.models[ParserPostDefault.__name__])
|
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@is_admin_or_owner_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def post(self, tenant_id: str):
|
|
args = ParserPostDefault.model_validate(console_ns.payload)
|
|
model_provider_service = ModelProviderService()
|
|
model_settings = args.model_settings
|
|
for model_setting in model_settings:
|
|
if model_setting.provider is None:
|
|
continue
|
|
|
|
try:
|
|
model_provider_service.update_default_model_of_model_type(
|
|
tenant_id=tenant_id,
|
|
model_type=model_setting.model_type,
|
|
provider=model_setting.provider,
|
|
model=cast(str, model_setting.model),
|
|
)
|
|
except Exception as ex:
|
|
logger.exception(
|
|
"Failed to update default model, model type: %s, model: %s",
|
|
model_setting.model_type,
|
|
model_setting.model,
|
|
)
|
|
raise ex
|
|
|
|
return {"result": "success"}
|
|
|
|
|
|
@console_ns.route("/workspaces/current/model-providers/<path:provider>/models")
|
|
class ModelProviderModelApi(Resource):
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def get(self, tenant_id: str, provider):
|
|
model_provider_service = ModelProviderService()
|
|
models = model_provider_service.get_models_by_provider(tenant_id=tenant_id, provider=provider)
|
|
|
|
return jsonable_encoder({"data": models})
|
|
|
|
@console_ns.expect(console_ns.models[ParserPostModels.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@is_admin_or_owner_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def post(self, tenant_id: str, provider: str):
|
|
# To save the model's load balance configs
|
|
args = ParserPostModels.model_validate(console_ns.payload)
|
|
|
|
if args.config_from == "custom-model":
|
|
if not args.credential_id:
|
|
raise ValueError("credential_id is required when configuring a custom-model")
|
|
service = ModelProviderService()
|
|
service.switch_active_custom_model_credential(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
model_type=args.model_type,
|
|
model=args.model,
|
|
credential_id=args.credential_id,
|
|
)
|
|
|
|
model_load_balancing_service = ModelLoadBalancingService()
|
|
|
|
if args.load_balancing and args.load_balancing.configs:
|
|
# save load balancing configs
|
|
model_load_balancing_service.update_load_balancing_configs(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
model=args.model,
|
|
model_type=args.model_type,
|
|
configs=args.load_balancing.configs,
|
|
config_from=args.config_from or "",
|
|
)
|
|
|
|
if args.load_balancing.enabled:
|
|
model_load_balancing_service.enable_model_load_balancing(
|
|
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
|
)
|
|
else:
|
|
model_load_balancing_service.disable_model_load_balancing(
|
|
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
|
)
|
|
|
|
return {"result": "success"}, 200
|
|
|
|
@console_ns.expect(console_ns.models[ParserDeleteModels.__name__])
|
|
@console_ns.response(204, "Model deleted successfully")
|
|
@setup_required
|
|
@login_required
|
|
@is_admin_or_owner_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def delete(self, tenant_id: str, provider: str):
|
|
args = ParserDeleteModels.model_validate(console_ns.payload)
|
|
|
|
model_provider_service = ModelProviderService()
|
|
model_provider_service.remove_model(
|
|
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
|
)
|
|
|
|
return "", 204
|
|
|
|
|
|
@console_ns.route("/workspaces/current/model-providers/<path:provider>/models/credentials")
|
|
class ModelProviderModelCredentialApi(Resource):
|
|
@console_ns.expect(console_ns.models[ParserGetCredentials.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def get(self, tenant_id: str, provider: str):
|
|
args = ParserGetCredentials.model_validate(request.args.to_dict(flat=True))
|
|
|
|
model_provider_service = ModelProviderService()
|
|
current_credential = model_provider_service.get_model_credential(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
model_type=args.model_type,
|
|
model=args.model,
|
|
credential_id=args.credential_id,
|
|
)
|
|
|
|
model_load_balancing_service = ModelLoadBalancingService()
|
|
is_load_balancing_enabled, load_balancing_configs = model_load_balancing_service.get_load_balancing_configs(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
model=args.model,
|
|
model_type=args.model_type,
|
|
config_from=args.config_from or "",
|
|
)
|
|
|
|
if args.config_from == "predefined-model":
|
|
available_credentials = model_provider_service.get_provider_available_credentials(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
)
|
|
else:
|
|
available_credentials = model_provider_service.get_provider_model_available_credentials(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
model_type=args.model_type,
|
|
model=args.model,
|
|
)
|
|
|
|
return jsonable_encoder(
|
|
{
|
|
"credentials": current_credential.get("credentials") if current_credential else {},
|
|
"current_credential_id": current_credential.get("current_credential_id")
|
|
if current_credential
|
|
else None,
|
|
"current_credential_name": current_credential.get("current_credential_name")
|
|
if current_credential
|
|
else None,
|
|
"load_balancing": {"enabled": is_load_balancing_enabled, "configs": load_balancing_configs},
|
|
"available_credentials": available_credentials,
|
|
}
|
|
)
|
|
|
|
@console_ns.expect(console_ns.models[ParserCreateCredential.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@is_admin_or_owner_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def post(self, tenant_id: str, provider: str):
|
|
args = ParserCreateCredential.model_validate(console_ns.payload)
|
|
|
|
model_provider_service = ModelProviderService()
|
|
|
|
try:
|
|
model_provider_service.create_model_credential(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
model=args.model,
|
|
model_type=args.model_type,
|
|
credentials=args.credentials,
|
|
credential_name=args.name,
|
|
)
|
|
except CredentialsValidateFailedError as ex:
|
|
logger.exception(
|
|
"Failed to save model credentials, tenant_id: %s, model: %s, model_type: %s",
|
|
tenant_id,
|
|
args.model,
|
|
args.model_type,
|
|
)
|
|
raise ValueError(str(ex))
|
|
|
|
return {"result": "success"}, 201
|
|
|
|
@console_ns.expect(console_ns.models[ParserUpdateCredential.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@is_admin_or_owner_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def put(self, current_tenant_id: str, provider: str):
|
|
args = ParserUpdateCredential.model_validate(console_ns.payload)
|
|
|
|
model_provider_service = ModelProviderService()
|
|
|
|
try:
|
|
model_provider_service.update_model_credential(
|
|
tenant_id=current_tenant_id,
|
|
provider=provider,
|
|
model_type=args.model_type,
|
|
model=args.model,
|
|
credentials=args.credentials,
|
|
credential_id=args.credential_id,
|
|
credential_name=args.name,
|
|
)
|
|
except CredentialsValidateFailedError as ex:
|
|
raise ValueError(str(ex))
|
|
|
|
return {"result": "success"}
|
|
|
|
@console_ns.expect(console_ns.models[ParserDeleteCredential.__name__])
|
|
@console_ns.response(204, "Credential deleted successfully")
|
|
@setup_required
|
|
@login_required
|
|
@is_admin_or_owner_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def delete(self, current_tenant_id: str, provider: str):
|
|
args = ParserDeleteCredential.model_validate(console_ns.payload)
|
|
|
|
model_provider_service = ModelProviderService()
|
|
model_provider_service.remove_model_credential(
|
|
tenant_id=current_tenant_id,
|
|
provider=provider,
|
|
model_type=args.model_type,
|
|
model=args.model,
|
|
credential_id=args.credential_id,
|
|
)
|
|
|
|
return "", 204
|
|
|
|
|
|
@console_ns.route("/workspaces/current/model-providers/<path:provider>/models/credentials/switch")
|
|
class ModelProviderModelCredentialSwitchApi(Resource):
|
|
@console_ns.expect(console_ns.models[ParserSwitch.__name__])
|
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@is_admin_or_owner_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def post(self, current_tenant_id: str, provider: str):
|
|
args = ParserSwitch.model_validate(console_ns.payload)
|
|
|
|
service = ModelProviderService()
|
|
service.add_model_credential_to_model_list(
|
|
tenant_id=current_tenant_id,
|
|
provider=provider,
|
|
model_type=args.model_type,
|
|
model=args.model,
|
|
credential_id=args.credential_id,
|
|
)
|
|
return {"result": "success"}
|
|
|
|
|
|
@console_ns.route(
|
|
"/workspaces/current/model-providers/<path:provider>/models/enable", endpoint="model-provider-model-enable"
|
|
)
|
|
class ModelProviderModelEnableApi(Resource):
|
|
@console_ns.expect(console_ns.models[ParserDeleteModels.__name__])
|
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def patch(self, tenant_id: str, provider: str):
|
|
args = ParserDeleteModels.model_validate(console_ns.payload)
|
|
|
|
model_provider_service = ModelProviderService()
|
|
model_provider_service.enable_model(
|
|
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
|
)
|
|
|
|
return {"result": "success"}
|
|
|
|
|
|
@console_ns.route(
|
|
"/workspaces/current/model-providers/<path:provider>/models/disable", endpoint="model-provider-model-disable"
|
|
)
|
|
class ModelProviderModelDisableApi(Resource):
|
|
@console_ns.expect(console_ns.models[ParserDeleteModels.__name__])
|
|
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def patch(self, tenant_id: str, provider: str):
|
|
args = ParserDeleteModels.model_validate(console_ns.payload)
|
|
|
|
model_provider_service = ModelProviderService()
|
|
model_provider_service.disable_model(
|
|
tenant_id=tenant_id, provider=provider, model=args.model, model_type=args.model_type
|
|
)
|
|
|
|
return {"result": "success"}
|
|
|
|
|
|
class ParserValidate(BaseModel):
|
|
model: str
|
|
model_type: ModelType
|
|
credentials: dict[str, Any]
|
|
|
|
|
|
register_schema_models(console_ns, ParserSwitch, ParserValidate)
|
|
|
|
|
|
@console_ns.route("/workspaces/current/model-providers/<path:provider>/models/credentials/validate")
|
|
class ModelProviderModelValidateApi(Resource):
|
|
@console_ns.expect(console_ns.models[ParserValidate.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def post(self, tenant_id: str, provider: str):
|
|
args = ParserValidate.model_validate(console_ns.payload)
|
|
|
|
model_provider_service = ModelProviderService()
|
|
|
|
result = True
|
|
error = ""
|
|
|
|
try:
|
|
model_provider_service.validate_model_credentials(
|
|
tenant_id=tenant_id,
|
|
provider=provider,
|
|
model=args.model,
|
|
model_type=args.model_type,
|
|
credentials=args.credentials,
|
|
)
|
|
except CredentialsValidateFailedError as ex:
|
|
result = False
|
|
error = str(ex)
|
|
|
|
response = {"result": "success" if result else "error"}
|
|
|
|
if not result:
|
|
response["error"] = error or ""
|
|
|
|
return response
|
|
|
|
|
|
@console_ns.route("/workspaces/current/model-providers/<path:provider>/models/parameter-rules")
|
|
class ModelProviderModelParameterRuleApi(Resource):
|
|
@console_ns.expect(console_ns.models[ParserParameter.__name__])
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def get(self, tenant_id: str, provider: str):
|
|
args = ParserParameter.model_validate(request.args.to_dict(flat=True))
|
|
|
|
model_provider_service = ModelProviderService()
|
|
parameter_rules = model_provider_service.get_model_parameter_rules(
|
|
tenant_id=tenant_id, provider=provider, model=args.model
|
|
)
|
|
|
|
return jsonable_encoder({"data": parameter_rules})
|
|
|
|
|
|
@console_ns.route("/workspaces/current/models/model-types/<string:model_type>")
|
|
class ModelProviderAvailableModelApi(Resource):
|
|
@setup_required
|
|
@login_required
|
|
@account_initialization_required
|
|
@with_current_tenant_id
|
|
def get(self, tenant_id: str, model_type: str):
|
|
model_provider_service = ModelProviderService()
|
|
models = model_provider_service.get_models_by_model_type(tenant_id=tenant_id, model_type=model_type)
|
|
|
|
return jsonable_encoder({"data": models})
|