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