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