You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 10 месеца
преди 10 месеца
преди 10 месеца
преди 10 месеца
преди 10 месеца
преди 10 месеца
преди 10 месеца
преди 10 месеца
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. import json
  2. import logging
  3. from typing import Optional, TypedDict, cast
  4. from flask_sqlalchemy.pagination import Pagination
  5. from configs import dify_config
  6. from constants.model_template import default_app_templates
  7. from core.agent.entities import AgentToolEntity
  8. from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
  9. from core.model_manager import ModelManager
  10. from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
  11. from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
  12. from core.tools.tool_manager import ToolManager
  13. from core.tools.utils.configuration import ToolParameterConfigurationManager
  14. from events.app_event import app_was_created
  15. from extensions.ext_database import db
  16. from libs.datetime_utils import naive_utc_now
  17. from libs.login import current_user
  18. from models.account import Account
  19. from models.model import App, AppMode, AppModelConfig, Site
  20. from models.tools import ApiToolProvider
  21. from services.enterprise.enterprise_service import EnterpriseService
  22. from services.feature_service import FeatureService
  23. from services.tag_service import TagService
  24. from tasks.remove_app_and_related_data_task import remove_app_and_related_data_task
  25. logger = logging.getLogger(__name__)
  26. class AppService:
  27. def get_paginate_apps(self, user_id: str, tenant_id: str, args: dict) -> Pagination | None:
  28. """
  29. Get app list with pagination
  30. :param user_id: user id
  31. :param tenant_id: tenant id
  32. :param args: request args
  33. :return:
  34. """
  35. filters = [App.tenant_id == tenant_id, App.is_universal == False]
  36. if args["mode"] == "workflow":
  37. filters.append(App.mode == AppMode.WORKFLOW.value)
  38. elif args["mode"] == "completion":
  39. filters.append(App.mode == AppMode.COMPLETION.value)
  40. elif args["mode"] == "chat":
  41. filters.append(App.mode == AppMode.CHAT.value)
  42. elif args["mode"] == "advanced-chat":
  43. filters.append(App.mode == AppMode.ADVANCED_CHAT.value)
  44. elif args["mode"] == "agent-chat":
  45. filters.append(App.mode == AppMode.AGENT_CHAT.value)
  46. if args.get("is_created_by_me", False):
  47. filters.append(App.created_by == user_id)
  48. if args.get("name"):
  49. name = args["name"][:30]
  50. filters.append(App.name.ilike(f"%{name}%"))
  51. # Check if tag_ids is not empty to avoid WHERE false condition
  52. if args.get("tag_ids") and len(args["tag_ids"]) > 0:
  53. target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"])
  54. if target_ids and len(target_ids) > 0:
  55. filters.append(App.id.in_(target_ids))
  56. else:
  57. return None
  58. app_models = db.paginate(
  59. db.select(App).where(*filters).order_by(App.created_at.desc()),
  60. page=args["page"],
  61. per_page=args["limit"],
  62. error_out=False,
  63. )
  64. return app_models
  65. def create_app(self, tenant_id: str, args: dict, account: Account) -> App:
  66. """
  67. Create app
  68. :param tenant_id: tenant id
  69. :param args: request args
  70. :param account: Account instance
  71. """
  72. app_mode = AppMode.value_of(args["mode"])
  73. app_template = default_app_templates[app_mode]
  74. # get model config
  75. default_model_config = app_template.get("model_config")
  76. default_model_config = default_model_config.copy() if default_model_config else None
  77. if default_model_config and "model" in default_model_config:
  78. # get model provider
  79. model_manager = ModelManager()
  80. # get default model instance
  81. try:
  82. model_instance = model_manager.get_default_model_instance(
  83. tenant_id=account.current_tenant_id or "", model_type=ModelType.LLM
  84. )
  85. except (ProviderTokenNotInitError, LLMBadRequestError):
  86. model_instance = None
  87. except Exception:
  88. logger.exception("Get default model instance failed, tenant_id: %s", tenant_id)
  89. model_instance = None
  90. if model_instance:
  91. if (
  92. model_instance.model == default_model_config["model"]["name"]
  93. and model_instance.provider == default_model_config["model"]["provider"]
  94. ):
  95. default_model_dict = default_model_config["model"]
  96. else:
  97. llm_model = cast(LargeLanguageModel, model_instance.model_type_instance)
  98. model_schema = llm_model.get_model_schema(model_instance.model, model_instance.credentials)
  99. if model_schema is None:
  100. raise ValueError(f"model schema not found for model {model_instance.model}")
  101. default_model_dict = {
  102. "provider": model_instance.provider,
  103. "name": model_instance.model,
  104. "mode": model_schema.model_properties.get(ModelPropertyKey.MODE),
  105. "completion_params": {},
  106. }
  107. else:
  108. provider, model = model_manager.get_default_provider_model_name(
  109. tenant_id=account.current_tenant_id or "", model_type=ModelType.LLM
  110. )
  111. default_model_config["model"]["provider"] = provider
  112. default_model_config["model"]["name"] = model
  113. default_model_dict = default_model_config["model"]
  114. default_model_config["model"] = json.dumps(default_model_dict)
  115. app = App(**app_template["app"])
  116. app.name = args["name"]
  117. app.description = args.get("description", "")
  118. app.mode = args["mode"]
  119. app.icon_type = args.get("icon_type", "emoji")
  120. app.icon = args["icon"]
  121. app.icon_background = args["icon_background"]
  122. app.tenant_id = tenant_id
  123. app.api_rph = args.get("api_rph", 0)
  124. app.api_rpm = args.get("api_rpm", 0)
  125. app.created_by = account.id
  126. app.updated_by = account.id
  127. db.session.add(app)
  128. db.session.flush()
  129. if default_model_config:
  130. app_model_config = AppModelConfig(**default_model_config)
  131. app_model_config.app_id = app.id
  132. app_model_config.created_by = account.id
  133. app_model_config.updated_by = account.id
  134. db.session.add(app_model_config)
  135. db.session.flush()
  136. app.app_model_config_id = app_model_config.id
  137. db.session.commit()
  138. app_was_created.send(app, account=account)
  139. if FeatureService.get_system_features().webapp_auth.enabled:
  140. # update web app setting as private
  141. EnterpriseService.WebAppAuth.update_app_access_mode(app.id, "private")
  142. return app
  143. def get_app(self, app: App) -> App:
  144. """
  145. Get App
  146. """
  147. assert isinstance(current_user, Account)
  148. assert current_user.current_tenant_id is not None
  149. # get original app model config
  150. if app.mode == AppMode.AGENT_CHAT.value or app.is_agent:
  151. model_config = app.app_model_config
  152. if not model_config:
  153. return app
  154. agent_mode = model_config.agent_mode_dict
  155. # decrypt agent tool parameters if it's secret-input
  156. for tool in agent_mode.get("tools") or []:
  157. if not isinstance(tool, dict) or len(tool.keys()) <= 3:
  158. continue
  159. agent_tool_entity = AgentToolEntity(**tool)
  160. # get tool
  161. try:
  162. tool_runtime = ToolManager.get_agent_tool_runtime(
  163. tenant_id=current_user.current_tenant_id,
  164. app_id=app.id,
  165. agent_tool=agent_tool_entity,
  166. )
  167. manager = ToolParameterConfigurationManager(
  168. tenant_id=current_user.current_tenant_id,
  169. tool_runtime=tool_runtime,
  170. provider_name=agent_tool_entity.provider_id,
  171. provider_type=agent_tool_entity.provider_type,
  172. identity_id=f"AGENT.{app.id}",
  173. )
  174. # get decrypted parameters
  175. if agent_tool_entity.tool_parameters:
  176. parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
  177. masked_parameter = manager.mask_tool_parameters(parameters or {})
  178. else:
  179. masked_parameter = {}
  180. # override tool parameters
  181. tool["tool_parameters"] = masked_parameter
  182. except Exception:
  183. pass
  184. # override agent mode
  185. if model_config:
  186. model_config.agent_mode = json.dumps(agent_mode)
  187. class ModifiedApp(App):
  188. """
  189. Modified App class
  190. """
  191. def __init__(self, app):
  192. self.__dict__.update(app.__dict__)
  193. @property
  194. def app_model_config(self):
  195. return model_config
  196. app = ModifiedApp(app)
  197. return app
  198. class ArgsDict(TypedDict):
  199. name: str
  200. description: str
  201. icon_type: str
  202. icon: str
  203. icon_background: str
  204. use_icon_as_answer_icon: bool
  205. max_active_requests: int
  206. def update_app(self, app: App, args: ArgsDict) -> App:
  207. """
  208. Update app
  209. :param app: App instance
  210. :param args: request args
  211. :return: App instance
  212. """
  213. assert current_user is not None
  214. app.name = args["name"]
  215. app.description = args["description"]
  216. app.icon_type = args["icon_type"]
  217. app.icon = args["icon"]
  218. app.icon_background = args["icon_background"]
  219. app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False)
  220. app.max_active_requests = args.get("max_active_requests")
  221. app.updated_by = current_user.id
  222. app.updated_at = naive_utc_now()
  223. db.session.commit()
  224. return app
  225. def update_app_name(self, app: App, name: str) -> App:
  226. """
  227. Update app name
  228. :param app: App instance
  229. :param name: new name
  230. :return: App instance
  231. """
  232. assert current_user is not None
  233. app.name = name
  234. app.updated_by = current_user.id
  235. app.updated_at = naive_utc_now()
  236. db.session.commit()
  237. return app
  238. def update_app_icon(self, app: App, icon: str, icon_background: str) -> App:
  239. """
  240. Update app icon
  241. :param app: App instance
  242. :param icon: new icon
  243. :param icon_background: new icon_background
  244. :return: App instance
  245. """
  246. assert current_user is not None
  247. app.icon = icon
  248. app.icon_background = icon_background
  249. app.updated_by = current_user.id
  250. app.updated_at = naive_utc_now()
  251. db.session.commit()
  252. return app
  253. def update_app_site_status(self, app: App, enable_site: bool) -> App:
  254. """
  255. Update app site status
  256. :param app: App instance
  257. :param enable_site: enable site status
  258. :return: App instance
  259. """
  260. if enable_site == app.enable_site:
  261. return app
  262. assert current_user is not None
  263. app.enable_site = enable_site
  264. app.updated_by = current_user.id
  265. app.updated_at = naive_utc_now()
  266. db.session.commit()
  267. return app
  268. def update_app_api_status(self, app: App, enable_api: bool) -> App:
  269. """
  270. Update app api status
  271. :param app: App instance
  272. :param enable_api: enable api status
  273. :return: App instance
  274. """
  275. if enable_api == app.enable_api:
  276. return app
  277. assert current_user is not None
  278. app.enable_api = enable_api
  279. app.updated_by = current_user.id
  280. app.updated_at = naive_utc_now()
  281. db.session.commit()
  282. return app
  283. def delete_app(self, app: App):
  284. """
  285. Delete app
  286. :param app: App instance
  287. """
  288. db.session.delete(app)
  289. db.session.commit()
  290. # clean up web app settings
  291. if FeatureService.get_system_features().webapp_auth.enabled:
  292. EnterpriseService.WebAppAuth.cleanup_webapp(app.id)
  293. # Trigger asynchronous deletion of app and related data
  294. remove_app_and_related_data_task.delay(tenant_id=app.tenant_id, app_id=app.id)
  295. def get_app_meta(self, app_model: App):
  296. """
  297. Get app meta info
  298. :param app_model: app model
  299. :return:
  300. """
  301. app_mode = AppMode.value_of(app_model.mode)
  302. meta: dict = {"tool_icons": {}}
  303. if app_mode in {AppMode.ADVANCED_CHAT, AppMode.WORKFLOW}:
  304. workflow = app_model.workflow
  305. if workflow is None:
  306. return meta
  307. graph = workflow.graph_dict
  308. nodes = graph.get("nodes", [])
  309. tools = []
  310. for node in nodes:
  311. if node.get("data", {}).get("type") == "tool":
  312. node_data = node.get("data", {})
  313. tools.append(
  314. {
  315. "provider_type": node_data.get("provider_type"),
  316. "provider_id": node_data.get("provider_id"),
  317. "tool_name": node_data.get("tool_name"),
  318. "tool_parameters": {},
  319. }
  320. )
  321. else:
  322. app_model_config: Optional[AppModelConfig] = app_model.app_model_config
  323. if not app_model_config:
  324. return meta
  325. agent_config = app_model_config.agent_mode_dict
  326. # get all tools
  327. tools = agent_config.get("tools", [])
  328. url_prefix = dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/builtin/"
  329. for tool in tools:
  330. keys = list(tool.keys())
  331. if len(keys) >= 4:
  332. # current tool standard
  333. provider_type = tool.get("provider_type", "")
  334. provider_id = tool.get("provider_id", "")
  335. tool_name = tool.get("tool_name", "")
  336. if provider_type == "builtin":
  337. meta["tool_icons"][tool_name] = url_prefix + provider_id + "/icon"
  338. elif provider_type == "api":
  339. try:
  340. provider: Optional[ApiToolProvider] = (
  341. db.session.query(ApiToolProvider).where(ApiToolProvider.id == provider_id).first()
  342. )
  343. if provider is None:
  344. raise ValueError(f"provider not found for tool {tool_name}")
  345. meta["tool_icons"][tool_name] = json.loads(provider.icon)
  346. except:
  347. meta["tool_icons"][tool_name] = {"background": "#252525", "content": "\ud83d\ude01"}
  348. return meta
  349. @staticmethod
  350. def get_app_code_by_id(app_id: str) -> str:
  351. """
  352. Get app code by app id
  353. :param app_id: app id
  354. :return: app code
  355. """
  356. site = db.session.query(Site).where(Site.app_id == app_id).first()
  357. if not site:
  358. raise ValueError(f"App with id {app_id} not found")
  359. return str(site.code)
  360. @staticmethod
  361. def get_app_id_by_code(app_code: str) -> str:
  362. """
  363. Get app id by app code
  364. :param app_code: app code
  365. :return: app id
  366. """
  367. site = db.session.query(Site).where(Site.code == app_code).first()
  368. if not site:
  369. raise ValueError(f"App with code {app_code} not found")
  370. return str(site.app_id)