import json import logging from datetime import datetime, timezone from typing import cast from flask_login import current_user from flask_sqlalchemy.pagination import Pagination from configs import dify_config from constants.model_template import default_app_templates from core.agent.entities import AgentToolEntity from core.app.features.rate_limiting import RateLimit from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.tools.tool_manager import ToolManager from core.tools.utils.configuration import ToolParameterConfigurationManager from events.app_event import app_was_created from extensions.ext_database import db from models.account import Account from models.model import App, AppMode, AppModelConfig from models.tools import ApiToolProvider from services.tag_service import TagService from tasks.remove_app_and_related_data_task import remove_app_and_related_data_task class AppService: def get_paginate_apps(self, tenant_id: str, args: dict) -> Pagination | None: """ Get app list with pagination :param tenant_id: tenant id :param args: request args :return: """ filters = [App.tenant_id == tenant_id, App.is_universal == False] if args["mode"] == "workflow": filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value])) elif args["mode"] == "chat": filters.append(App.mode.in_([AppMode.CHAT.value, AppMode.ADVANCED_CHAT.value])) elif args["mode"] == "agent-chat": filters.append(App.mode == AppMode.AGENT_CHAT.value) elif args["mode"] == "channel": filters.append(App.mode == AppMode.CHANNEL.value) if args.get("name"): name = args["name"][:30] filters.append(App.name.ilike(f"%{name}%")) if args.get("tag_ids"): target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"]) if target_ids: filters.append(App.id.in_(target_ids)) else: return None app_models = db.paginate( db.select(App).where(*filters).order_by(App.created_at.desc()), page=args["page"], per_page=args["limit"], error_out=False, ) return app_models def create_app(self, tenant_id: str, args: dict, account: Account) -> App: """ Create app :param tenant_id: tenant id :param args: request args :param account: Account instance """ app_mode = AppMode.value_of(args["mode"]) app_template = default_app_templates[app_mode] # get model config default_model_config = app_template.get("model_config") default_model_config = default_model_config.copy() if default_model_config else None if default_model_config and "model" in default_model_config: # get model provider model_manager = ModelManager() # get default model instance try: model_instance = model_manager.get_default_model_instance( tenant_id=account.current_tenant_id, model_type=ModelType.LLM ) except (ProviderTokenNotInitError, LLMBadRequestError): model_instance = None except Exception as e: logging.exception(e) model_instance = None if model_instance: if ( model_instance.model == default_model_config["model"]["name"] and model_instance.provider == default_model_config["model"]["provider"] ): default_model_dict = default_model_config["model"] else: llm_model = cast(LargeLanguageModel, model_instance.model_type_instance) model_schema = llm_model.get_model_schema(model_instance.model, model_instance.credentials) default_model_dict = { "provider": model_instance.provider, "name": model_instance.model, "mode": model_schema.model_properties.get(ModelPropertyKey.MODE), "completion_params": {}, } else: provider, model = model_manager.get_default_provider_model_name( tenant_id=account.current_tenant_id, model_type=ModelType.LLM ) default_model_config["model"]["provider"] = provider default_model_config["model"]["name"] = model default_model_dict = default_model_config["model"] default_model_config["model"] = json.dumps(default_model_dict) app = App(**app_template["app"]) app.name = args["name"] app.description = args.get("description", "") app.mode = args["mode"] app.icon_type = args.get("icon_type", "emoji") app.icon = args["icon"] app.icon_background = args["icon_background"] app.tenant_id = tenant_id app.api_rph = args.get("api_rph", 0) app.api_rpm = args.get("api_rpm", 0) app.created_by = account.id app.updated_by = account.id db.session.add(app) db.session.flush() if default_model_config: app_model_config = AppModelConfig(**default_model_config) app_model_config.app_id = app.id app_model_config.created_by = account.id app_model_config.updated_by = account.id db.session.add(app_model_config) db.session.flush() app.app_model_config_id = app_model_config.id db.session.commit() app_was_created.send(app, account=account) return app def get_app(self, app: App) -> App: """ Get App """ # get original app model config if app.mode == AppMode.AGENT_CHAT.value or app.is_agent: model_config: AppModelConfig = app.app_model_config agent_mode = model_config.agent_mode_dict # decrypt agent tool parameters if it's secret-input for tool in agent_mode.get("tools") or []: if not isinstance(tool, dict) or len(tool.keys()) <= 3: continue agent_tool_entity = AgentToolEntity(**tool) # get tool try: tool_runtime = ToolManager.get_agent_tool_runtime( tenant_id=current_user.current_tenant_id, app_id=app.id, agent_tool=agent_tool_entity, ) manager = ToolParameterConfigurationManager( tenant_id=current_user.current_tenant_id, tool_runtime=tool_runtime, provider_name=agent_tool_entity.provider_id, provider_type=agent_tool_entity.provider_type, identity_id=f"AGENT.{app.id}", ) # get decrypted parameters if agent_tool_entity.tool_parameters: parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {}) masked_parameter = manager.mask_tool_parameters(parameters or {}) else: masked_parameter = {} # override tool parameters tool["tool_parameters"] = masked_parameter except Exception as e: pass # override agent mode model_config.agent_mode = json.dumps(agent_mode) class ModifiedApp(App): """ Modified App class """ def __init__(self, app): self.__dict__.update(app.__dict__) @property def app_model_config(self): return model_config app = ModifiedApp(app) return app def update_app(self, app: App, args: dict) -> App: """ Update app :param app: App instance :param args: request args :return: App instance """ app.name = args.get("name") app.description = args.get("description", "") app.max_active_requests = args.get("max_active_requests") app.icon_type = args.get("icon_type", "emoji") app.icon = args.get("icon") app.icon_background = args.get("icon_background") app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False) app.updated_by = current_user.id app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None) db.session.commit() if app.max_active_requests is not None: rate_limit = RateLimit(app.id, app.max_active_requests) rate_limit.flush_cache(use_local_value=True) return app def update_app_name(self, app: App, name: str) -> App: """ Update app name :param app: App instance :param name: new name :return: App instance """ app.name = name app.updated_by = current_user.id app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None) db.session.commit() return app def update_app_icon(self, app: App, icon: str, icon_background: str) -> App: """ Update app icon :param app: App instance :param icon: new icon :param icon_background: new icon_background :return: App instance """ app.icon = icon app.icon_background = icon_background app.updated_by = current_user.id app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None) db.session.commit() return app def update_app_site_status(self, app: App, enable_site: bool) -> App: """ Update app site status :param app: App instance :param enable_site: enable site status :return: App instance """ if enable_site == app.enable_site: return app app.enable_site = enable_site app.updated_by = current_user.id app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None) db.session.commit() return app def update_app_api_status(self, app: App, enable_api: bool) -> App: """ Update app api status :param app: App instance :param enable_api: enable api status :return: App instance """ if enable_api == app.enable_api: return app app.enable_api = enable_api app.updated_by = current_user.id app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None) db.session.commit() return app def delete_app(self, app: App) -> None: """ Delete app :param app: App instance """ db.session.delete(app) db.session.commit() # Trigger asynchronous deletion of app and related data remove_app_and_related_data_task.delay(tenant_id=app.tenant_id, app_id=app.id) def get_app_meta(self, app_model: App) -> dict: """ Get app meta info :param app_model: app model :return: """ app_mode = AppMode.value_of(app_model.mode) meta = {"tool_icons": {}} if app_mode in {AppMode.ADVANCED_CHAT, AppMode.WORKFLOW}: workflow = app_model.workflow if workflow is None: return meta graph = workflow.graph_dict nodes = graph.get("nodes", []) tools = [] for node in nodes: if node.get("data", {}).get("type") == "tool": node_data = node.get("data", {}) tools.append( { "provider_type": node_data.get("provider_type"), "provider_id": node_data.get("provider_id"), "tool_name": node_data.get("tool_name"), "tool_parameters": {}, } ) else: app_model_config: AppModelConfig = app_model.app_model_config if not app_model_config: return meta agent_config = app_model_config.agent_mode_dict or {} # get all tools tools = agent_config.get("tools", []) url_prefix = dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/builtin/" for tool in tools: keys = list(tool.keys()) if len(keys) >= 4: # current tool standard provider_type = tool.get("provider_type") provider_id = tool.get("provider_id") tool_name = tool.get("tool_name") if provider_type == "builtin": meta["tool_icons"][tool_name] = url_prefix + provider_id + "/icon" elif provider_type == "api": try: provider: ApiToolProvider = ( db.session.query(ApiToolProvider).filter(ApiToolProvider.id == provider_id).first() ) meta["tool_icons"][tool_name] = json.loads(provider.icon) except: meta["tool_icons"][tool_name] = {"background": "#252525", "content": "\ud83d\ude01"} return meta