From 479a9a715e47322dc6126185b80bec8e817abbea Mon Sep 17 00:00:00 2001 From: buua436 Date: Thu, 25 Jun 2026 13:04:21 +0800 Subject: [PATCH] feat: unify provider id or name routing (#16336) --- api/apps/restful_apis/provider_api.py | 170 +++++------ api/apps/services/provider_api_service.py | 281 ++++++++++++------ .../services/tenant_model_provider_service.py | 8 + 3 files changed, 281 insertions(+), 178 deletions(-) diff --git a/api/apps/restful_apis/provider_api.py b/api/apps/restful_apis/provider_api.py index 85554073d2..d86628a1ce 100644 --- a/api/apps/restful_apis/provider_api.py +++ b/api/apps/restful_apis/provider_api.py @@ -120,9 +120,9 @@ async def add_provider(tenant_id: str = None): return get_error_data_result(message="Internal server error") -@manager.route("/providers/", methods=["GET"]) # noqa: F821 +@manager.route("/providers/", methods=["GET"]) # noqa: F821 @login_required -def show_provider(provider_name: str): +def show_provider(provider_id_or_name: str): """ Show provider details. --- @@ -132,10 +132,10 @@ def show_provider(provider_name: str): - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: header name: Authorization type: string @@ -148,7 +148,7 @@ def show_provider(provider_name: str): type: object """ try: - success, result = provider_api_service.show_provider(provider_name) + success, result = provider_api_service.show_provider(provider_id_or_name) if success: return get_result(data=result) else: @@ -158,10 +158,10 @@ def show_provider(provider_name: str): return get_error_data_result(message="Internal server error") -@manager.route("/providers/", methods=["DELETE"]) # noqa: F821 +@manager.route("/providers/", methods=["DELETE"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -def delete_provider(tenant_id: str = None, provider_name: str = None): +def delete_provider(tenant_id: str = None, provider_id_or_name: str = None): """ Delete a provider and all its models for the tenant. --- @@ -171,10 +171,10 @@ def delete_provider(tenant_id: str = None, provider_name: str = None): - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: header name: Authorization type: string @@ -187,7 +187,7 @@ def delete_provider(tenant_id: str = None, provider_name: str = None): type: object """ try: - success, msg = provider_api_service.delete_provider(tenant_id, provider_name) + success, msg = provider_api_service.delete_provider(tenant_id, provider_id_or_name) if success: return get_result(message=msg) else: @@ -197,9 +197,9 @@ def delete_provider(tenant_id: str = None, provider_name: str = None): return get_error_data_result(message="Internal server error") -@manager.route("/providers//models", methods=["GET"]) # noqa: F821 +@manager.route("/providers//models", methods=["GET"]) # noqa: F821 @login_required -async def list_provider_models(provider_name: str): +async def list_provider_models(provider_id_or_name: str): """ List models for a provider. --- @@ -209,10 +209,10 @@ async def list_provider_models(provider_name: str): - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: header name: Authorization type: string @@ -232,7 +232,7 @@ async def list_provider_models(provider_name: str): try: api_key = request.args.get("api_key") base_url = request.args.get("base_url") - success, result = await provider_api_service.list_provider_models(provider_name, api_key, base_url) + success, result = await provider_api_service.list_provider_models(provider_id_or_name, api_key, base_url) if success: return get_result(data=result) else: @@ -242,9 +242,9 @@ async def list_provider_models(provider_name: str): return get_error_data_result(message="Internal server error") -@manager.route("/providers//models/", methods=["GET"]) # noqa: F821 +@manager.route("/providers//models/", methods=["GET"]) # noqa: F821 @login_required -def show_provider_model(provider_name: str, model_name: str): +def show_provider_model(provider_id_or_name: str, model_name: str): """ Show a specific model for a provider. --- @@ -254,10 +254,10 @@ def show_provider_model(provider_name: str, model_name: str): - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: path name: model_name type: string @@ -275,7 +275,7 @@ def show_provider_model(provider_name: str, model_name: str): type: object """ try: - success, result = provider_api_service.show_provider_model(provider_name, model_name) + success, result = provider_api_service.show_provider_model(provider_id_or_name, model_name) if success: return get_result(data=result) else: @@ -285,10 +285,10 @@ def show_provider_model(provider_name: str, model_name: str): return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances", methods=["POST"]) # noqa: F821 +@manager.route("/providers//instances", methods=["POST"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -async def create_provider_instance(tenant_id: str = None, provider_name: str = None): +async def create_provider_instance(tenant_id: str = None, provider_id_or_name: str = None): """ Create a provider instance. --- @@ -298,10 +298,10 @@ async def create_provider_instance(tenant_id: str = None, provider_name: str = N - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: header name: Authorization type: string @@ -346,7 +346,7 @@ async def create_provider_instance(tenant_id: str = None, provider_name: str = N model_info = data.get("model_info", []) try: - success, msg = await provider_api_service.create_provider_instance(tenant_id, provider_name, instance_name, api_key, base_url, region, model_info) + success, msg = await provider_api_service.create_provider_instance(tenant_id, provider_id_or_name, instance_name, api_key, base_url, region, model_info) if success: return get_result(message=msg) else: @@ -356,9 +356,9 @@ async def create_provider_instance(tenant_id: str = None, provider_name: str = N return get_error_data_result(message="Internal server error") -@manager.route("/providers//connection", methods=["POST"]) # noqa: F821 +@manager.route("/providers//connection", methods=["POST"]) # noqa: F821 @login_required -async def verify_provider_api_key(provider_name: str = None): +async def verify_provider_api_key(provider_id_or_name: str = None): """ Verify api key. --- @@ -368,10 +368,10 @@ async def verify_provider_api_key(provider_name: str = None): - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: header name: Authorization type: string @@ -414,7 +414,7 @@ async def verify_provider_api_key(provider_name: str = None): model_info = data.get("model_info", []) try: - success, msg = await provider_api_service.verify_api_key(provider_name, api_key, base_url, region, model_info) + success, msg = await provider_api_service.verify_api_key(provider_id_or_name, api_key, base_url, region, model_info) if success: return get_result(message=msg) else: @@ -424,10 +424,10 @@ async def verify_provider_api_key(provider_name: str = None): return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances", methods=["GET"]) # noqa: F821 +@manager.route("/providers//instances", methods=["GET"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -def list_provider_instances(tenant_id: str = None, provider_name: str = None): +def list_provider_instances(tenant_id: str = None, provider_id_or_name: str = None): """ List provider instances. --- @@ -437,10 +437,10 @@ def list_provider_instances(tenant_id: str = None, provider_name: str = None): - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: header name: Authorization type: string @@ -458,7 +458,7 @@ def list_provider_instances(tenant_id: str = None, provider_name: str = None): type: object """ try: - success, result = provider_api_service.list_provider_instances(tenant_id, provider_name) + success, result = provider_api_service.list_provider_instances(tenant_id, provider_id_or_name) if success: return get_result(data=result) else: @@ -468,10 +468,10 @@ def list_provider_instances(tenant_id: str = None, provider_name: str = None): return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances/", methods=["GET"]) # noqa: F821 +@manager.route("/providers//instances/", methods=["GET"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -def show_provider_instance(tenant_id: str = None, provider_name: str = None, instance_name: str = None): +def show_provider_instance(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None): """ Show a provider instance. --- @@ -481,15 +481,15 @@ def show_provider_instance(tenant_id: str = None, provider_name: str = None, ins - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: path - name: instance_name + name: instance_id_or_name type: string required: true - description: Instance name. + description: Instance ID or name. - in: header name: Authorization type: string @@ -502,7 +502,7 @@ def show_provider_instance(tenant_id: str = None, provider_name: str = None, ins type: object """ try: - success, result = provider_api_service.show_provider_instance(tenant_id, provider_name, instance_name) + success, result = provider_api_service.show_provider_instance(tenant_id, provider_id_or_name, instance_id_or_name) if success: return get_result(data=result) else: @@ -512,10 +512,10 @@ def show_provider_instance(tenant_id: str = None, provider_name: str = None, ins return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances", methods=["DELETE"]) # noqa: F821 +@manager.route("/providers//instances", methods=["DELETE"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -async def drop_provider_instances(tenant_id: str = None, provider_name: str = None): +async def drop_provider_instances(tenant_id: str = None, provider_id_or_name: str = None): """ Drop provider instances. --- @@ -525,10 +525,10 @@ async def drop_provider_instances(tenant_id: str = None, provider_name: str = No - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: header name: Authorization type: string @@ -547,7 +547,7 @@ async def drop_provider_instances(tenant_id: str = None, provider_name: str = No type: array items: type: string - description: List of instance names to drop. + description: List of instance IDs or names to drop. responses: 200: description: Instances dropped successfully. @@ -563,7 +563,7 @@ async def drop_provider_instances(tenant_id: str = None, provider_name: str = No return get_error_argument_result(message="instances is required") try: - success, msg = provider_api_service.drop_provider_instances(tenant_id, provider_name, instances) + success, msg = provider_api_service.drop_provider_instances(tenant_id, provider_id_or_name, instances) if success: return get_result(message=msg) else: @@ -573,10 +573,10 @@ async def drop_provider_instances(tenant_id: str = None, provider_name: str = No return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances//models", methods=["GET"]) # noqa: F821 +@manager.route("/providers//instances//models", methods=["GET"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -def list_instance_models(tenant_id: str = None, provider_name: str = None, instance_name: str = None): +def list_instance_models(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None): """ List models for a provider instance. --- @@ -586,15 +586,15 @@ def list_instance_models(tenant_id: str = None, provider_name: str = None, insta - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: path - name: instance_name + name: instance_id_or_name type: string required: true - description: Instance name. + description: Instance ID or name. - in: query name: supported type: string @@ -619,7 +619,7 @@ def list_instance_models(tenant_id: str = None, provider_name: str = None, insta supported_only = request.args.get("supported", "").lower() == "true" try: success, result = provider_api_service.list_instance_models( - tenant_id, provider_name, instance_name, supported_only + tenant_id, provider_id_or_name, instance_id_or_name, supported_only ) if success: return get_result(data=result) @@ -630,10 +630,10 @@ def list_instance_models(tenant_id: str = None, provider_name: str = None, insta return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances//models", methods=["PUT"]) # noqa: F821 +@manager.route("/providers//instances//models", methods=["PUT"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -async def update_instance_models(tenant_id: str, provider_name: str, instance_name: str): +async def update_instance_models(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str): """ Batch update model_type for models in instance. --- @@ -643,15 +643,15 @@ async def update_instance_models(tenant_id: str, provider_name: str, instance_na - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: path - name: instance_name + name: instance_id_or_name type: string required: true - description: Instance name. + description: Instance ID or name. - in: header name: Authorization type: string @@ -680,7 +680,9 @@ async def update_instance_models(tenant_id: str, provider_name: str, instance_na model_name = data["model_name"] model_type = data["model_type"] try: - success, msg = provider_api_service.update_instance_models(tenant_id, provider_name, instance_name, model_name, model_type) + success, msg = provider_api_service.update_instance_models( + tenant_id, provider_id_or_name, instance_id_or_name, model_name, model_type + ) if success: return get_result(message=msg) else: @@ -690,10 +692,10 @@ async def update_instance_models(tenant_id: str, provider_name: str, instance_na return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances//models", methods=["POST"]) # noqa: F821 +@manager.route("/providers//instances//models", methods=["POST"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -async def add_model_to_instance(tenant_id: str, provider_name: str, instance_name: str): +async def add_model_to_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str): """ Add a model to an instance. --- @@ -703,15 +705,15 @@ async def add_model_to_instance(tenant_id: str, provider_name: str, instance_nam - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: path - name: instance_name + name: instance_id_or_name type: string required: true - description: Instance name. + description: Instance ID or name. - in: header name: Authorization type: string @@ -754,7 +756,7 @@ async def add_model_to_instance(tenant_id: str, provider_name: str, instance_nam try: success, result = provider_api_service.add_model_to_instance( - tenant_id, provider_name, instance_name, model_name, model_type, max_tokens, extra + tenant_id, provider_id_or_name, instance_id_or_name, model_name, model_type, max_tokens, extra ) if success: return get_result(message=result) @@ -765,10 +767,10 @@ async def add_model_to_instance(tenant_id: str, provider_name: str, instance_nam return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances//models/", methods=["PATCH"]) # noqa: F821 +@manager.route("/providers//instances//models/", methods=["PATCH"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -async def enable_or_disable_model(tenant_id: str = None, provider_name: str = None, instance_name: str = None, model_name: str = None): +async def enable_or_disable_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. --- @@ -778,15 +780,15 @@ async def enable_or_disable_model(tenant_id: str = None, provider_name: str = No - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: path - name: instance_name + name: instance_id_or_name type: string required: true - description: Instance name. + description: Instance ID or name. - in: path name: model_name type: string @@ -825,7 +827,9 @@ async def enable_or_disable_model(tenant_id: str = None, provider_name: str = No return get_error_argument_result(message="status must be 'active' or 'inactive'") try: - success, msg = provider_api_service.update_model_status(tenant_id, provider_name, instance_name, model_name, status) + success, msg = provider_api_service.update_model_status( + tenant_id, provider_id_or_name, instance_id_or_name, model_name, status + ) if success: return get_result(message=msg) else: @@ -835,10 +839,10 @@ async def enable_or_disable_model(tenant_id: str = None, provider_name: str = No return get_error_data_result(message="Internal server error") -@manager.route("/providers//instances//models/", methods=["POST"]) # noqa: F821 +@manager.route("/providers//instances//models/", methods=["POST"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs -async def chat_to_model(tenant_id: str = None, provider_name: str = None, instance_name: str = None, model_name: str = None): +async def chat_to_model(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None, model_name: str = None): """ Chat to a model. --- @@ -848,15 +852,15 @@ async def chat_to_model(tenant_id: str = None, provider_name: str = None, instan - ApiKeyAuth: [] parameters: - in: path - name: provider_name + name: provider_id_or_name type: string required: true - description: Provider name. + description: Provider ID or name. - in: path - name: instance_name + name: instance_id_or_name type: string required: true - description: Instance name. + description: Instance ID or name. - in: path name: model_name type: string @@ -901,7 +905,7 @@ async def chat_to_model(tenant_id: str = None, provider_name: str = None, instan try: success, result = await provider_api_service.chat_to_model( - tenant_id, provider_name, instance_name, model_name, message, stream, thinking + tenant_id, provider_id_or_name, instance_id_or_name, model_name, message, stream, thinking ) if not success: return get_error_data_result(message=result) diff --git a/api/apps/services/provider_api_service.py b/api/apps/services/provider_api_service.py index 8b489b897d..031cdca773 100644 --- a/api/apps/services/provider_api_service.py +++ b/api/apps/services/provider_api_service.py @@ -157,37 +157,42 @@ def add_provider(tenant_id: str, provider_name: str): return True, "success" -def delete_provider(tenant_id: str, provider_name: str): +def delete_provider(tenant_id: str, provider_id_or_name: str): """ Delete all instances and models for a provider. :param tenant_id: tenant ID - :param provider_name: provider/factory name + :param provider_id_or_name: provider ID or provider/factory name :return: (success, result_or_error_message) """ - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"Provider {provider_name} not found" + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"Provider {provider_id_or_name} not found" instance_objs = TenantModelInstanceService.get_all_by_provider_id(provider_obj.id) - if not instance_objs: - return False, f"No instances found for provider {provider_name}" - instance_ids = [instance_obj.id for instance_obj in instance_objs] - delete_models_by_instance_ids(instance_ids) - delete_instances_by_provider_ids([provider_obj.id]) - TenantModelProviderService.delete_by_tenant_id_and_provider_name(tenant_id, provider_name) + if instance_objs: + instance_ids = [instance_obj.id for instance_obj in instance_objs] + delete_models_by_instance_ids(instance_ids) + delete_instances_by_provider_ids([provider_obj.id]) + TenantModelProviderService.delete_by_tenant_id_and_provider_name(tenant_id, provider_obj.provider_name) return True, "success" -def show_provider(provider_name: str): +def show_provider(provider_id_or_name: str): """ Show provider details from LLMFactories. - :param provider_name: provider/factory name + :param provider_id_or_name: provider/factory ID or name :return: (success, result_or_error_message) """ + provider_obj = None + if provider_id_or_name: + _, provider_obj = TenantModelProviderService.get_by_id(provider_id_or_name) + provider_name = provider_obj.provider_name if provider_obj else provider_id_or_name fac_list = [f for f in FACTORY_LLM_INFOS if f["name"]==provider_name] if not fac_list: - return False, f"Provider '{provider_name}' not found" + return False, f"Provider '{provider_id_or_name}' not found" factory_info = fac_list[0] return True, { "base_url": { @@ -198,18 +203,22 @@ def show_provider(provider_name: str): } -async def list_provider_models(provider_name: str, api_key: str = None, base_url: str = None): +async def list_provider_models(provider_id_or_name: str, api_key: str = None, base_url: str = None): """ List all models for a provider from the LLM dictionary. - :param provider_name: provider/factory name + :param provider_id_or_name: provider ID or provider/factory name :param api_key: api key :param base_url: base url :return: (success, result_or_error_message) """ - factory_info = [f for f in FACTORY_LLM_INFOS if f["name"]==provider_name] + provider_obj = None + if provider_id_or_name: + _, provider_obj = TenantModelProviderService.get_by_id(provider_id_or_name) + provider_name = provider_obj.provider_name if provider_obj else provider_id_or_name + factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_name] if not factory_info: - return False, f"Provider '{provider_name}' not found" + return False, f"Provider '{provider_id_or_name}' not found" static_llms = [{ "name": _factory_llm_name(llm), "max_tokens": llm["max_tokens"], @@ -230,7 +239,7 @@ async def list_provider_models(provider_name: str, api_key: str = None, base_url remote_models = await ModelMeta[provider_name](api_key, model_base_url).get_model_list() if not static_llms and not remote_models: - return False, f"No models found for provider '{provider_name}'" + return False, f"No models found for provider '{provider_id_or_name}'" # Merge static and remote models, preferring remote_models on name conflicts merged = {m["name"]: m for m in static_llms} @@ -241,20 +250,24 @@ async def list_provider_models(provider_name: str, api_key: str = None, base_url return True, models -def show_provider_model(provider_name: str, model_name: str): +def show_provider_model(provider_id_or_name: str, model_name: str): """ Show a specific model for a provider. - :param provider_name: provider/factory name + :param provider_id_or_name: provider/factory ID or name :param model_name: model name :return: (success, result_or_error_message) """ + provider_obj = None + if provider_id_or_name: + _, provider_obj = TenantModelProviderService.get_by_id(provider_id_or_name) + provider_name = provider_obj.provider_name if provider_obj else provider_id_or_name factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_name] if not factory_info: - return False, f"Provider '{provider_name}' not found" + return False, f"Provider '{provider_id_or_name}' not found" llms = factory_info[0]["llm"] if not llms: - return False, f"No models found for provider '{provider_name}'" + return False, f"No models found for provider '{provider_id_or_name}'" target_llm = [llm for llm in llms if _factory_llm_name(llm) == model_name] if not target_llm: return False, f"Model '{model_name}' not found" @@ -269,7 +282,7 @@ def show_provider_model(provider_name: str, model_name: str): } -async def create_provider_instance(tenant_id: str, provider_name: str, instance_name: str, api_key: str|dict, base_url: str, region: str, model_info: list[dict]=None): +async def create_provider_instance(tenant_id: str, provider_id_or_name: str, instance_name: str, api_key: str|dict, base_url: str, region: str, model_info: list[dict]=None): """ Create a provider instance. @@ -277,7 +290,7 @@ async def create_provider_instance(tenant_id: str, provider_name: str, instance_ model all records under a factory share the same API key configuration. :param tenant_id: tenant ID - :param provider_name: provider/factory name + :param provider_id_or_name: provider/factory ID or name :param instance_name: instance name (used as a logical identifier) :param api_key: API key :param base_url: base url @@ -293,8 +306,16 @@ async def create_provider_instance(tenant_id: str, provider_name: str, instance_ }] :return: (success, result_or_error_message) """ - if not provider_name: - return False, "Provider name is required" + if not provider_id_or_name: + return False, "Provider ID or name is required" + + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) + if not provider_obj: + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"Provider '{provider_id_or_name}' does not exist" + + provider_name = provider_obj.provider_name base_url = _normalize_provider_base_url(provider_name, base_url) @@ -306,10 +327,6 @@ async def create_provider_instance(tenant_id: str, provider_name: str, instance_ if provider_name not in allowed_factories: return False, f"Provider '{provider_name}' is not allowed" - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) - if not provider_obj: - return False, f"Provider '{provider_name}' does not exist" - api_key_str = "" if api_key: api_key_str = api_key if isinstance(api_key, str) else json.dumps(api_key) @@ -335,17 +352,19 @@ async def create_provider_instance(tenant_id: str, provider_name: str, instance_ return True, "success" -def list_provider_instances(tenant_id: str, provider_name: str): +def list_provider_instances(tenant_id: str, provider_id_or_name: str): """ List provider instances for a tenant. :param tenant_id: tenant ID - :param provider_name: provider/factory name + :param provider_id_or_name: provider/factory ID or name :return: (success, result_or_error_message) """ - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"No provider found for provider '{provider_name}'" + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" provider_id = provider_obj.id instance_objs = TenantModelInstanceService.get_all_by_provider_id(provider_id) if not instance_objs: @@ -364,11 +383,11 @@ def list_provider_instances(tenant_id: str, provider_name: str): return True, instances -async def verify_api_key(provider_name: str, api_key: str|dict, base_url: str=None, region: str=None, model_info: list[dict]=None): +async def verify_api_key(provider_id_or_name: str, api_key: str|dict, base_url: str=None, region: str=None, model_info: list[dict]=None): """ Verify API key for a provider. - :param provider_name: provider/factory name + :param provider_id_or_name: provider/factory ID or name :param api_key: API key :param base_url: base url :param region: region @@ -383,8 +402,13 @@ async def verify_api_key(provider_name: str, api_key: str|dict, base_url: str=No }] :return: (success, result_or_error_message) """ - if not provider_name: - return False, "Provider name is required" + if not provider_id_or_name: + return False, "Provider ID or name is required" + + provider_obj = None + if provider_id_or_name: + _, provider_obj = TenantModelProviderService.get_by_id(provider_id_or_name) + provider_name = provider_obj.provider_name if provider_obj else provider_id_or_name base_url = _normalize_provider_base_url(provider_name, base_url) @@ -395,18 +419,18 @@ async def verify_api_key(provider_name: str, api_key: str|dict, base_url: str=No factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == target_factory_name] if not factory_info: - return False, f"Provider '{provider_name}' not found" + return False, f"Provider '{provider_id_or_name}' not found" factory_llms = factory_info[0]["llm"] if not factory_llms: if not model_info: - return False, f"No models found for provider '{provider_name}'" + return False, f"No models found for provider '{provider_id_or_name}'" factory_llms = [{ "model_type": _type, "llm_name": model.get("model_name", ""), } for model in model_info if model for _type in model.get("model_type", []) ] if not factory_llms: - return False, f"No valid models found for provider '{provider_name}'" + return False, f"No valid models found for provider '{provider_id_or_name}'" # test if api key works chat_passed, embd_passed, rerank_passed, ocr_passed, tts_passed = False, False, False, False, False @@ -535,22 +559,30 @@ async def verify_api_key(provider_name: str, api_key: str|dict, base_url: str=No return success, "success" if success else msg -def show_provider_instance(tenant_id: str, provider_name: str, instance_name: str): +def show_provider_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str): """ Show a specific provider instance. :param tenant_id: tenant ID - :param provider_name: provider/factory name - :param instance_name: instance name + :param provider_id_or_name: provider/factory ID or name + :param instance_id_or_name: instance ID or name :return: (success, result_or_error_message) """ - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"No provider found for provider '{provider_name}'" + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" provider_id = provider_obj.id - instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_id, instance_name) + instance_obj = None + if instance_id_or_name: + _, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name) + if instance_obj and instance_obj.provider_id != provider_id: + instance_obj = None if not instance_obj: - return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'" + instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_id, instance_id_or_name) + if not instance_obj: + return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" extra_fields = json.loads(instance_obj.extra) if instance_obj.extra else {} return True, { @@ -562,30 +594,38 @@ def show_provider_instance(tenant_id: str, provider_name: str, instance_name: st } -def drop_provider_instances(tenant_id: str, provider_name: str, instance_names: list): +def drop_provider_instances(tenant_id: str, provider_id_or_name: str, instance_id_or_names: list): """ Drop provider instances. for the specified models/instances. :param tenant_id: tenant ID - :param provider_name: provider/factory name - :param instance_names: list of instance names to drop + :param provider_id_or_name: provider/factory ID or name + :param instance_id_or_names: list of instance IDs or names to drop :return: (success, result_or_error_message) """ - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"No provider found for provider '{provider_name}'" + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" provider_id = provider_obj.id not_exist_instances = [] instance_ids = [] - for instance_name in instance_names: - instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_id, instance_name) + for instance_id_or_name in instance_id_or_names: + instance_obj = None + if instance_id_or_name: + _, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name) + if instance_obj and instance_obj.provider_id != provider_id: + instance_obj = None if not instance_obj: - not_exist_instances.append(instance_name) + instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_id, instance_id_or_name) + if not instance_obj: + not_exist_instances.append(instance_id_or_name) continue instance_ids.append(instance_obj.id) if not_exist_instances: - return False, f"No instance found for provider '{provider_name}' and instance '{not_exist_instances}'" + return False, f"No instance found for provider '{provider_id_or_name}' and instance '{not_exist_instances}'" delete_models_by_instance_ids(instance_ids) TenantModelInstanceService.delete_by_ids(instance_ids) return True, None @@ -642,7 +682,7 @@ def _hybrid_get_instance_models(provider_name: str, instance_id: str): return True, models -def list_instance_models(tenant_id: str, provider_name: str, instance_name: str, supported_only: bool = False): +def list_instance_models(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, supported_only: bool = False): """ List models for a provider instance. @@ -652,52 +692,68 @@ def list_instance_models(tenant_id: str, provider_name: str, instance_name: str, - Models present in tenant_model table are marked "inactive", others "active". :param tenant_id: tenant ID - :param provider_name: provider/factory name - :param instance_name: instance name + :param provider_id_or_name: provider/factory ID or name + :param instance_id_or_name: instance ID or name :param supported_only: if True, only list supported models (from LLM dictionary) :return: (success, result_or_error_message) """ - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"No provider found for provider '{provider_name}'" + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" if supported_only: # List all models supported by this provider from the LLM dictionary - factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_name] + factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_obj.provider_name] if not factory_info: - return False, f"Provider '{provider_name}' not found" + return False, f"Provider '{provider_id_or_name}' not found" llms = factory_info[0].get("llm", []) models = [{"name": llm["llm_name"]} for llm in llms] models.sort(key=lambda x: x["name"]) return True, models # Get instance - instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name) + instance_obj = None + if instance_id_or_name: + _, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name) + if instance_obj and instance_obj.provider_id != provider_obj.id: + instance_obj = None if not instance_obj: - return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'" + instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name) + if not instance_obj: + return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" - return _hybrid_get_instance_models(provider_name, instance_obj.id) + return _hybrid_get_instance_models(provider_obj.provider_name, instance_obj.id) -def update_instance_models(tenant_id: str, provider_name: str, instance_name: str, model_names: list, model_types: list): +def update_instance_models(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_names: list, model_types: list): if not model_names or not model_types: return False, "model_name and model_type are required" - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"No provider found for provider '{provider_name}'" - instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" + instance_obj = None + if instance_id_or_name: + _, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name) + if instance_obj and instance_obj.provider_id != provider_obj.id: + instance_obj = None if not instance_obj: - return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'" + instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name) + if not instance_obj: + return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" - found, models = _hybrid_get_instance_models(provider_name, instance_obj.id) + found, models = _hybrid_get_instance_models(provider_obj.provider_name, instance_obj.id) if not found: return False, models model_info_map = {model["name"]: model for model in models} not_exist_models = set(model_names) - set(model_info_map.keys()) if not_exist_models: - return False, f"Models {not_exist_models} not found for provider '{provider_name}' and instance '{instance_name}'" + return False, f"Models {not_exist_models} not found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" for model_name in model_names: model_info = model_info_map.get(model_name, {}) TenantModelService.upsert_model_type( @@ -712,19 +768,27 @@ def update_instance_models(tenant_id: str, provider_name: str, instance_name: st return True, "success" -def add_model_to_instance(tenant_id: str, provider_name: str, instance_name: str, model_name: str, model_type: str|list[str], max_tokens: int=8192, extra: dict=None): - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) +def add_model_to_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_name: str, model_type: str|list[str], max_tokens: int=8192, extra: dict=None): + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"No provider found for provider '{provider_name}'" - instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" + instance_obj = None + if instance_id_or_name: + _, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name) + if instance_obj and instance_obj.provider_id != provider_obj.id: + instance_obj = None if not instance_obj: - return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'" + instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name) + if not instance_obj: + return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" model_obj = TenantModelService.get_by_provider_id_and_instance_id_and_model_name(provider_obj.id, instance_obj.id, model_name) if model_obj: - return False, f"Model '{model_name}' already exists for provider '{provider_name}' and instance '{instance_name}'" - factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_name] + return False, f"Model '{model_name}' already exists for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" + factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_obj.provider_name] if not factory_info: - return False, f"Provider '{provider_name}' not found" + return False, f"Provider '{provider_id_or_name}' not found" llms = factory_info[0].get("llm", []) if isinstance(model_type, str): model_type = [model_type] @@ -747,7 +811,7 @@ def add_model_to_instance(tenant_id: str, provider_name: str, instance_name: str return True, "success" -def update_model_status(tenant_id: str, provider_name: str, instance_name: str, model_name: str, status: str): +def update_model_status(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_name: str, status: str): """ Enable or disable a model for a provider instance. @@ -757,8 +821,8 @@ def update_model_status(tenant_id: str, provider_name: str, instance_name: str, - status="inactive": create a record with status="inactive". :param tenant_id: tenant ID - :param provider_name: provider/factory name - :param instance_name: instance name + :param provider_id_or_name: provider/factory ID or name + :param instance_id_or_name: instance ID or name :param model_name: model name :param status: "active" or "inactive" (ActiveStatusEnum values) :return: (success, result_or_error_message) @@ -767,14 +831,22 @@ def update_model_status(tenant_id: str, provider_name: str, instance_name: str, return False, f"status must be '{ActiveStatusEnum.ACTIVE.value}' or '{ActiveStatusEnum.INACTIVE.value}'" # Check if provider exists for this tenant - provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_name) + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) if not provider_obj: - return False, f"No provider found for provider '{provider_name}'" + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" # Check if instance exists - instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_name) + instance_obj = None + if instance_id_or_name: + _, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name) + if instance_obj and instance_obj.provider_id != provider_obj.id: + instance_obj = None if not instance_obj: - return False, f"No instance found for provider '{provider_name}' and instance '{instance_name}'" + instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name) + if not instance_obj: + return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" # Check if model record already exists in tenant_model table model_obj_list = TenantModelService.get_by_provider_id_and_instance_id_and_model_name( @@ -791,13 +863,13 @@ def update_model_status(tenant_id: str, provider_name: str, instance_name: str, return True, None # status is "inactive" — create a record with inactive status # Look up model schema from FACTORY_LLM_INFOS - factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_name] + factory_info = [f for f in FACTORY_LLM_INFOS if f["name"] == provider_obj.provider_name] if not factory_info: - return False, f"Provider '{provider_name}' not found" + return False, f"Provider '{provider_id_or_name}' not found" llms = factory_info[0].get("llm", []) target_llm = [llm for llm in llms if llm["llm_name"] == model_name] if not target_llm: - return False, f"provider {provider_name} model {model_name} not found" + return False, f"provider {provider_obj.provider_name} model {model_name} not found" for model_type in _factory_model_types(target_llm[0]): TenantModelService.insert( @@ -813,13 +885,13 @@ def update_model_status(tenant_id: str, provider_name: str, instance_name: str, return True, None -async def chat_to_model(tenant_id: str, provider_name: str, instance_name: str, model_name: str, message: str, stream: bool = False, thinking: bool = False): +async def chat_to_model(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str, model_name: str, message: str, stream: bool = False, thinking: bool = False): """ Chat to a model. :param tenant_id: tenant ID - :param provider_name: provider/factory name - :param instance_name: instance name + :param provider_id_or_name: provider/factory ID or name + :param instance_id_or_name: instance ID or name :param model_name: model name :param message: chat message :param stream: whether to stream the response @@ -828,6 +900,25 @@ async def chat_to_model(tenant_id: str, provider_name: str, instance_name: str, """ from api.db.services.llm_service import LLMBundle + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_id(tenant_id, provider_id_or_name) + if not provider_obj: + provider_obj = TenantModelProviderService.get_by_tenant_id_and_provider_name(tenant_id, provider_id_or_name) + if not provider_obj: + return False, f"No provider found for provider '{provider_id_or_name}'" + + instance_obj = None + if instance_id_or_name: + _, instance_obj = TenantModelInstanceService.get_by_id(instance_id_or_name) + if instance_obj and instance_obj.provider_id != provider_obj.id: + instance_obj = None + if not instance_obj: + instance_obj = TenantModelInstanceService.get_by_provider_id_and_instance_name(provider_obj.id, instance_id_or_name) + if not instance_obj: + return False, f"No instance found for provider '{provider_id_or_name}' and instance '{instance_id_or_name}'" + + provider_name = provider_obj.provider_name + instance_name = instance_obj.instance_name + # Get model config composite_name = f"{model_name}@{instance_name}@{provider_name}" try: diff --git a/api/db/services/tenant_model_provider_service.py b/api/db/services/tenant_model_provider_service.py index 1472175947..ee9a1ab951 100644 --- a/api/db/services/tenant_model_provider_service.py +++ b/api/db/services/tenant_model_provider_service.py @@ -28,6 +28,14 @@ class TenantModelProviderService(CommonService): cls.model.provider_name == provider_name, ) + @classmethod + @DB.connection_context() + def get_by_tenant_id_and_provider_id(cls, tenant_id, provider_id): + return cls.model.get_or_none( + cls.model.tenant_id == tenant_id, + cls.model.id == provider_id, + ) + @classmethod @DB.connection_context() def get_by_tenant_id(cls, tenant_id):