Underground-Digital/Workflow-Engine
0
1import json2import logging3from datetime import datetime, timezone4from typing import cast5 6from flask_login import current_user7from flask_sqlalchemy.pagination import Pagination8 9from configs import dify_config10from constants.model_template import default_app_templates11from core.agent.entities import AgentToolEntity12from core.app.features.rate_limiting import RateLimit13from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError14from core.model_manager import ModelManager15from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType16from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel17from core.tools.tool_manager import ToolManager18from core.tools.utils.configuration import ToolParameterConfigurationManager19from events.app_event import app_was_created20from extensions.ext_database import db21from models.account import Account22from models.model import App, AppMode, AppModelConfig23from models.tools import ApiToolProvider24from services.tag_service import TagService25from tasks.remove_app_and_related_data_task import remove_app_and_related_data_task26 27 28class AppService:29 def get_paginate_apps(self, tenant_id: str, args: dict) -> Pagination | None:30 """31 Get app list with pagination32 :param tenant_id: tenant id33 :param args: request args34 :return:35 """36 filters = [App.tenant_id == tenant_id, App.is_universal == False]37 38 if args["mode"] == "workflow":39 filters.append(App.mode.in_([AppMode.WORKFLOW.value, AppMode.COMPLETION.value]))40 elif args["mode"] == "chat":41 filters.append(App.mode.in_([AppMode.CHAT.value, AppMode.ADVANCED_CHAT.value]))42 elif args["mode"] == "agent-chat":43 filters.append(App.mode == AppMode.AGENT_CHAT.value)44 elif args["mode"] == "channel":45 filters.append(App.mode == AppMode.CHANNEL.value)46 47 if args.get("name"):48 name = args["name"][:30]49 filters.append(App.name.ilike(f"%{name}%"))50 if args.get("tag_ids"):51 target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"])52 if target_ids:53 filters.append(App.id.in_(target_ids))54 else:55 return None56 57 app_models = db.paginate(58 db.select(App).where(*filters).order_by(App.created_at.desc()),59 page=args["page"],60 per_page=args["limit"],61 error_out=False,62 )63 64 return app_models65 66 def create_app(self, tenant_id: str, args: dict, account: Account) -> App:67 """68 Create app69 :param tenant_id: tenant id70 :param args: request args71 :param account: Account instance72 """73 app_mode = AppMode.value_of(args["mode"])74 app_template = default_app_templates[app_mode]75 76 # get model config77 default_model_config = app_template.get("model_config")78 default_model_config = default_model_config.copy() if default_model_config else None79 if default_model_config and "model" in default_model_config:80 # get model provider81 model_manager = ModelManager()82 83 # get default model instance84 try:85 model_instance = model_manager.get_default_model_instance(86 tenant_id=account.current_tenant_id, model_type=ModelType.LLM87 )88 except (ProviderTokenNotInitError, LLMBadRequestError):89 model_instance = None90 except Exception as e:91 logging.exception(e)92 model_instance = None93 94 if model_instance:95 if (96 model_instance.model == default_model_config["model"]["name"]97 and model_instance.provider == default_model_config["model"]["provider"]98 ):99 default_model_dict = default_model_config["model"]100 else:101 llm_model = cast(LargeLanguageModel, model_instance.model_type_instance)102 model_schema = llm_model.get_model_schema(model_instance.model, model_instance.credentials)103 104 default_model_dict = {105 "provider": model_instance.provider,106 "name": model_instance.model,107 "mode": model_schema.model_properties.get(ModelPropertyKey.MODE),108 "completion_params": {},109 }110 else:111 provider, model = model_manager.get_default_provider_model_name(112 tenant_id=account.current_tenant_id, model_type=ModelType.LLM113 )114 default_model_config["model"]["provider"] = provider115 default_model_config["model"]["name"] = model116 default_model_dict = default_model_config["model"]117 118 default_model_config["model"] = json.dumps(default_model_dict)119 120 app = App(**app_template["app"])121 app.name = args["name"]122 app.description = args.get("description", "")123 app.mode = args["mode"]124 app.icon_type = args.get("icon_type", "emoji")125 app.icon = args["icon"]126 app.icon_background = args["icon_background"]127 app.tenant_id = tenant_id128 app.api_rph = args.get("api_rph", 0)129 app.api_rpm = args.get("api_rpm", 0)130 app.created_by = account.id131 app.updated_by = account.id132 133 db.session.add(app)134 db.session.flush()135 136 if default_model_config:137 app_model_config = AppModelConfig(**default_model_config)138 app_model_config.app_id = app.id139 app_model_config.created_by = account.id140 app_model_config.updated_by = account.id141 db.session.add(app_model_config)142 db.session.flush()143 144 app.app_model_config_id = app_model_config.id145 146 db.session.commit()147 148 app_was_created.send(app, account=account)149 150 return app151 152 def get_app(self, app: App) -> App:153 """154 Get App155 """156 # get original app model config157 if app.mode == AppMode.AGENT_CHAT.value or app.is_agent:158 model_config: AppModelConfig = app.app_model_config159 agent_mode = model_config.agent_mode_dict160 # decrypt agent tool parameters if it's secret-input161 for tool in agent_mode.get("tools") or []:162 if not isinstance(tool, dict) or len(tool.keys()) <= 3:163 continue164 agent_tool_entity = AgentToolEntity(**tool)165 # get tool166 try:167 tool_runtime = ToolManager.get_agent_tool_runtime(168 tenant_id=current_user.current_tenant_id,169 app_id=app.id,170 agent_tool=agent_tool_entity,171 )172 manager = ToolParameterConfigurationManager(173 tenant_id=current_user.current_tenant_id,174 tool_runtime=tool_runtime,175 provider_name=agent_tool_entity.provider_id,176 provider_type=agent_tool_entity.provider_type,177 identity_id=f"AGENT.{app.id}",178 )179 180 # get decrypted parameters181 if agent_tool_entity.tool_parameters:182 parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {})183 masked_parameter = manager.mask_tool_parameters(parameters or {})184 else:185 masked_parameter = {}186 187 # override tool parameters188 tool["tool_parameters"] = masked_parameter189 except Exception as e:190 pass191 192 # override agent mode193 model_config.agent_mode = json.dumps(agent_mode)194 195 class ModifiedApp(App):196 """197 Modified App class198 """199 200 def __init__(self, app):201 self.__dict__.update(app.__dict__)202 203 @property204 def app_model_config(self):205 return model_config206 207 app = ModifiedApp(app)208 209 return app210 211 def update_app(self, app: App, args: dict) -> App:212 """213 Update app214 :param app: App instance215 :param args: request args216 :return: App instance217 """218 app.name = args.get("name")219 app.description = args.get("description", "")220 app.max_active_requests = args.get("max_active_requests")221 app.icon_type = args.get("icon_type", "emoji")222 app.icon = args.get("icon")223 app.icon_background = args.get("icon_background")224 app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False)225 app.updated_by = current_user.id226 app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)227 db.session.commit()228 229 if app.max_active_requests is not None:230 rate_limit = RateLimit(app.id, app.max_active_requests)231 rate_limit.flush_cache(use_local_value=True)232 return app233 234 def update_app_name(self, app: App, name: str) -> App:235 """236 Update app name237 :param app: App instance238 :param name: new name239 :return: App instance240 """241 app.name = name242 app.updated_by = current_user.id243 app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)244 db.session.commit()245 246 return app247 248 def update_app_icon(self, app: App, icon: str, icon_background: str) -> App:249 """250 Update app icon251 :param app: App instance252 :param icon: new icon253 :param icon_background: new icon_background254 :return: App instance255 """256 app.icon = icon257 app.icon_background = icon_background258 app.updated_by = current_user.id259 app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)260 db.session.commit()261 262 return app263 264 def update_app_site_status(self, app: App, enable_site: bool) -> App:265 """266 Update app site status267 :param app: App instance268 :param enable_site: enable site status269 :return: App instance270 """271 if enable_site == app.enable_site:272 return app273 274 app.enable_site = enable_site275 app.updated_by = current_user.id276 app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)277 db.session.commit()278 279 return app280 281 def update_app_api_status(self, app: App, enable_api: bool) -> App:282 """283 Update app api status284 :param app: App instance285 :param enable_api: enable api status286 :return: App instance287 """288 if enable_api == app.enable_api:289 return app290 291 app.enable_api = enable_api292 app.updated_by = current_user.id293 app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None)294 db.session.commit()295 296 return app297 298 def delete_app(self, app: App) -> None:299 """300 Delete app301 :param app: App instance302 """303 db.session.delete(app)304 db.session.commit()305 306 # Trigger asynchronous deletion of app and related data307 remove_app_and_related_data_task.delay(tenant_id=app.tenant_id, app_id=app.id)308 309 def get_app_meta(self, app_model: App) -> dict:310 """311 Get app meta info312 :param app_model: app model313 :return:314 """315 app_mode = AppMode.value_of(app_model.mode)316 317 meta = {"tool_icons": {}}318 319 if app_mode in {AppMode.ADVANCED_CHAT, AppMode.WORKFLOW}:320 workflow = app_model.workflow321 if workflow is None:322 return meta323 324 graph = workflow.graph_dict325 nodes = graph.get("nodes", [])326 tools = []327 for node in nodes:328 if node.get("data", {}).get("type") == "tool":329 node_data = node.get("data", {})330 tools.append(331 {332 "provider_type": node_data.get("provider_type"),333 "provider_id": node_data.get("provider_id"),334 "tool_name": node_data.get("tool_name"),335 "tool_parameters": {},336 }337 )338 else:339 app_model_config: AppModelConfig = app_model.app_model_config340 341 if not app_model_config:342 return meta343 344 agent_config = app_model_config.agent_mode_dict or {}345 346 # get all tools347 tools = agent_config.get("tools", [])348 349 url_prefix = dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/builtin/"350 351 for tool in tools:352 keys = list(tool.keys())353 if len(keys) >= 4:354 # current tool standard355 provider_type = tool.get("provider_type")356 provider_id = tool.get("provider_id")357 tool_name = tool.get("tool_name")358 if provider_type == "builtin":359 meta["tool_icons"][tool_name] = url_prefix + provider_id + "/icon"360 elif provider_type == "api":361 try:362 provider: ApiToolProvider = (363 db.session.query(ApiToolProvider).filter(ApiToolProvider.id == provider_id).first()364 )365 meta["tool_icons"][tool_name] = json.loads(provider.icon)366 except:367 meta["tool_icons"][tool_name] = {"background": "#252525", "content": "\ud83d\ude01"}368 369 return meta370 