# # Copyright 2026 The InfiniFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import logging from quart import request from api.apps import login_required, current_user from api.utils.api_utils import ( add_tenant_id_to_kwargs, get_error_argument_result, get_error_data_result, get_result, ) from api.apps.services import provider_api_service @manager.route("/providers", methods=["GET"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs def list_providers(tenant_id: str = None): """ List providers. --- parameters: - in: query name: available type: string required: false description: "If 'true', list all available system providers; otherwise list tenant-configured providers." - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: List of providers. schema: type: object properties: data: type: array items: type: object """ available_only = request.args.get("available", "").lower() == "true" try: success, result = provider_api_service.list_providers(tenant_id, available_only) if success: return get_result(data=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers", methods=["PUT"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs async def add_provider(tenant_id: str = None): """ Add a provider for the tenant. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Provider creation parameters. required: true schema: type: object required: - provider_name properties: provider_name: type: string description: Provider/factory name. responses: 200: description: Provider added successfully. schema: type: object """ data = await request.get_json() if not data or "provider_name" not in data: return get_error_argument_result(message="provider_name is required") provider_name = data["provider_name"] try: success, msg = provider_api_service.add_provider(tenant_id, provider_name) if success: return get_result(message=msg) else: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers/", methods=["GET"]) # noqa: F821 @login_required def show_provider(provider_id_or_name: str): """ Show provider details. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: Provider details. schema: type: object """ try: success, result = provider_api_service.show_provider(provider_id_or_name) if success: return get_result(data=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers/", methods=["DELETE"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs def delete_provider(tenant_id: str = None, provider_id_or_name: str = None): """ Delete a provider and all its models for the tenant. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: Provider deleted successfully. schema: type: object """ try: success, msg = provider_api_service.delete_provider(tenant_id, provider_id_or_name) if success: return get_result(message=msg) else: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//models", methods=["GET"]) # noqa: F821 @login_required async def list_provider_models(provider_id_or_name: str): """ List models for a provider. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: List of models for the provider. schema: type: object properties: data: type: array items: type: object """ 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_id_or_name, api_key, base_url) if success: return get_result(data=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//models/", methods=["GET"]) # noqa: F821 @login_required def show_provider_model(provider_id_or_name: str, model_name: str): """ Show a specific model for a provider. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: model_name type: string required: true description: Model name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: Model details. schema: type: object """ try: success, result = provider_api_service.show_provider_model(provider_id_or_name, model_name) if success: return get_result(data=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @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_id_or_name: str = None): """ Create a provider instance. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Instance creation parameters. required: true schema: type: object required: - instance_name - api_key - base_url - region - model_info properties: instance_name: type: string description: Instance name. api_key: type: string description: API key. region: type: string description: Region. model_info: type: object description: Model info. responses: 200: description: Instance created successfully. schema: type: object """ data = await request.get_json() if not provider_id_or_name: return get_error_argument_result(message="provider_id_or_name is required") if not data or "instance_name" not in data: return get_error_argument_result(message="instance_name is required") instance_name = data["instance_name"] # data only contains instance_name — no other fields needed if set(data.keys()) == {"instance_name"}: try: success, msg = await provider_api_service.create_name_only_provider_instance(tenant_id, provider_id_or_name, instance_name) if success: return get_result(message=msg) else: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") api_key = data.get("api_key", "") base_url = data.get("base_url", "") region = data.get("region", "") model_info = data.get("model_info", []) try: 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: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//connection", methods=["POST"]) # noqa: F821 @login_required async def verify_provider_api_key(provider_id_or_name: str = None): """ Verify api key. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Instance creation parameters. required: true schema: type: object required: - api_key properties: api_key: type: string description: API key. base_url: type: string description: Base URL. region: type: string description: Region. model_info: type: object description: Model info. optional instance_id: type: string description: Instance ID. optional responses: 200: description: Instance created successfully. schema: type: object """ data = await request.get_json() if not provider_id_or_name: return get_error_argument_result(message="provider_id_or_name is required") if not data or ("api_key" not in data and provider_id_or_name != "VLLM"): return get_error_argument_result(message="api_key is required") base_url = data.get("base_url", "") api_key = data.get("api_key", "") region = data.get("region", "default") model_info = data.get("model_info", []) try: success, msg, model_verify_result = await provider_api_service.verify_api_key(provider_id_or_name, api_key, base_url, region, model_info) if success: if data.get("instance_id"): # if instance_id is provided, update the model verify result instance_id = data["instance_id"] try: for model, verify_result in model_verify_result.items(): provider_api_service.update_model(current_user.id, provider_id_or_name, instance_id, model, {"verify": verify_result}) except Exception as e: logging.exception(e) return get_result(message=msg) else: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//instances", methods=["GET"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs def list_provider_instances(tenant_id: str = None, provider_id_or_name: str = None): """ List provider instances. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: List of provider instances. schema: type: object properties: data: type: array items: type: object """ try: success, result = provider_api_service.list_provider_instances(tenant_id, provider_id_or_name) if success: return get_result(data=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//instances/", methods=["GET"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs def show_provider_instance(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None): """ Show a provider instance. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: Instance details. schema: type: object """ try: 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: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//instances/", methods=["PUT"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs async def update_provider_instance(tenant_id: str = None, provider_id_or_name: str = None, instance_id_or_name: str = None): """ Update a provider instance. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Instance update parameters. required: true schema: type: object required: - instance_name - api_key - base_url - region - model_info properties: instance_name: type: string description: Instance name. api_key: type: string description: API key. base_url: type: string description: Base URL. region: type: string description: Region. model_info: type: array description: List of models to configure for this instance. items: type: object properties: model_type: type: array description: Model types. model_name: type: string description: Model name. max_tokens: type: integer description: Max tokens. extra: type: object description: Extra model info (e.g. is_tools). verify: type: boolean description: Verify api_key and base_url, default true responses: 200: description: Instance updated successfully. schema: type: object """ data = await request.get_json() if not provider_id_or_name: return get_error_argument_result(message="provider_id_or_name is required") if not instance_id_or_name: return get_error_argument_result(message="instance_id_or_name is required") if not data: return get_error_argument_result(message="Request body is required") required_keys = ["instance_name", "api_key", "base_url", "model_info"] missing = [k for k in required_keys if k not in data] if missing: return get_error_argument_result(message=f"Missing required fields: {', '.join(missing)}") instance_name = data["instance_name"] api_key = data["api_key"] base_url = data["base_url"] region = data.get("region", "default") model_info = data["model_info"] 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 ) if success: return get_result(message=msg) else: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @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_id_or_name: str = None): """ Drop provider instances. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Instance deletion parameters. required: true schema: type: object required: - instances properties: instances: type: array items: type: string description: List of instance IDs or names to drop. responses: 200: description: Instances dropped successfully. schema: type: object """ data = await request.get_json() if not data or "instances" not in data: return get_error_argument_result(message="instances is required") instances = data["instances"] if not instances: return get_error_argument_result(message="instances is required") try: success, msg = provider_api_service.drop_provider_instances(tenant_id, provider_id_or_name, instances) if success: return get_result(message=msg) else: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @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_id_or_name: str = None, instance_id_or_name: str = None): """ List models for a provider instance. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: query name: supported type: string required: false description: "If 'true', list only supported models." - in: header name: Authorization type: string required: true description: Bearer token for authentication. responses: 200: description: List of models. schema: type: object properties: data: type: array items: type: object """ supported_only = request.args.get("supported", "").lower() == "true" try: success, result = provider_api_service.list_instance_models(tenant_id, provider_id_or_name, instance_id_or_name, supported_only) if success: return get_result(data=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @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_id_or_name: str, instance_id_or_name: str): """ Batch update model_type for models in instance. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Model details. required: true schema: type: object required: - model_name - model_type properties: model_name: type: list of string description: Model name. model_type: type: list of string description: Model type. """ data = await request.get_json() if not data or "model_name" not in data or "model_type" not in data: return get_error_argument_result(message="model_name and model_type are required") model_name = data["model_name"] model_type = data["model_type"] try: 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: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @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_id_or_name: str, instance_id_or_name: str): """ Add a model to an instance. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Model details. required: true schema: type: object required: - model_name - model_type properties: model_name: type: string description: Model name. model_type: type: string description: Model type. max_tokens: type: integer description: Maximum number of tokens. extra: type: object description: Extra model details. responses: 200: description: Model added successfully. """ data = await request.get_json() if not data or "model_name" not in data or "model_type" not in data: return get_error_argument_result(message="model_name and model_type are required") model_name = data["model_name"] model_type = data["model_type"] max_tokens = data.get("max_tokens", 8192) extra = data.get("extra", {}) try: success, result = provider_api_service.add_model_to_instance(tenant_id, provider_id_or_name, instance_id_or_name, model_name, model_type, max_tokens, extra) if success: return get_result(message=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//instances//models", methods=["DELETE"]) # noqa: F821 @login_required @add_tenant_id_to_kwargs async def delete_models_from_instance(tenant_id: str, provider_id_or_name: str, instance_id_or_name: str): """ Delete models from an instance. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Model details. required: true schema: type: object required: - model_name - model_type properties: model_name: type: list of string description: Model name. responses: 200: description: Model deleted successfully. """ data = await request.get_json() if not data or "model_name" not in data: return get_error_argument_result(message="model_name is required") model_name = data["model_name"] try: success, result = await provider_api_service.delete_models_from_instance(tenant_id, provider_id_or_name, instance_id_or_name, model_name) if success: return get_result(message=result) else: return get_error_data_result(message=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @manager.route("/providers//instances//models/", 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 --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: path name: model_name type: string required: true description: Model name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Model status update. required: true schema: type: object required: - status properties: status: type: string enum: ["active", "inactive"] description: Model status. responses: 200: description: Model status 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.") 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 ) if success: return get_result(message=msg) else: return get_error_data_result(message=msg) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error") @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_id_or_name: str = None, instance_id_or_name: str = None, model_name: str = None): """ Chat to a model. --- tags: - Providers security: - ApiKeyAuth: [] parameters: - in: path name: provider_id_or_name type: string required: true description: Provider ID or name. - in: path name: instance_id_or_name type: string required: true description: Instance ID or name. - in: path name: model_name type: string required: true description: Model name. - in: header name: Authorization type: string required: true description: Bearer token for authentication. - in: body name: body description: Chat request. required: true schema: type: object required: - message properties: message: type: string description: Chat message. stream: type: boolean description: Whether to stream the response. thinking: type: boolean description: Whether to enable thinking/reasoning. responses: 200: description: Chat response. schema: type: object """ data = await request.get_json() if not data or "message" not in data: return get_error_argument_result(message="message is required") message = data["message"] stream = data.get("stream", False) thinking = data.get("thinking", False) try: success, result = await provider_api_service.chat_to_model(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) if stream and isinstance(result, dict) and result.get("type") == "stream": # Streaming response using SSE from quart import Response llm = result["llm"] async def generate(): async for chunk in llm.async_chat_streamly( None, [{"role": "user", "content": message}], {"temperature": 0.9}, ): if chunk and isinstance(chunk, str) and chunk.find("**ERROR**") < 0: yield f"data: [MESSAGE]{chunk}\n\n" yield "data: [DONE]\n\n" return Response( generate(), mimetype="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", }, ) # Non-streaming response return get_result(data=result) except Exception as e: logging.exception(e) return get_error_data_result(message="Internal server error")