Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

10 месяцев назад
10 месяцев назад
10 месяцев назад
10 месяцев назад
10 месяцев назад
10 месяцев назад
10 месяцев назад
10 месяцев назад
10 месяцев назад
4 месяцев назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. import json
  2. import logging
  3. from datetime import UTC, datetime
  4. from typing import Optional, cast
  5. from flask_login import current_user
  6. from flask_sqlalchemy.pagination import Pagination
  7. from configs import dify_config
  8. from constants.model_template import default_app_templates
  9. from core.agent.entities import AgentToolEntity
  10. from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
  11. from core.model_manager import ModelManager
  12. from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
  13. from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel
  14. from core.tools.tool_manager import ToolManager
  15. from core.tools.utils.configuration import ToolParameterConfigurationManager
  16. from events.app_event import app_was_created
  17. from extensions.ext_database import db
  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. elif args["mode"] == "channel":
  46. filters.append(App.mode == AppMode.CHANNEL.value)
  47. if args.get("is_created_by_me", False):
  48. filters.append(App.created_by == user_id)
  49. if args.get("name"):
  50. name = args["name"][:30]
  51. filters.append(App.name.ilike(f"%{name}%"))
  52. if args.get("tag_ids"):
  53. target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"])
  54. if target_ids:
  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 as e:
  88. logging.exception(f"Get default model instance failed, tenant_id: {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. # get original app model config
  148. if app.mode == AppMode.AGENT_CHAT.value or app.is_agent:
  149. model_config = app.app_model_config
  150. agent_mode = model_config.agent_mode_dict
  151. # decrypt agent tool parameters if it's secret-input
  152. for tool in agent_mode.get("tools") or []:
  153. if not isinstance(tool, dict) or len(tool.keys()) <= 3:
  154. continue
  155. agent_tool_entity = AgentToolEntity(**tool)
  156. # get tool
  157. try:
  158. tool_runtime = ToolManager.get_agent_tool_runtime(
  159. tenant_id=current_user.current_tenant_id,
  160. app_id=app.id,
  161. agent_tool=agent_tool_entity,
  162. )
  163. manager = ToolParameterConfigurationManager(
  164. tenant_id=current_user.current_tenant_id,
  165. tool_runtime=tool_runtime,
  166. provider_name=agent_tool_entity.provider_id,
  167. provider_type=agent_tool_entity.provider_type,
  168. identity_id=f"AGENT.{app.id}",
  169. )
  170. # get decrypted parameters
  171. if agent_tool_entity.tool_parameters:
  172. parameters = manager.decrypt_tool_parameters(agent_tool_entity.tool_parameters or {})
  173. masked_parameter = manager.mask_tool_parameters(parameters or {})
  174. else:
  175. masked_parameter = {}
  176. # override tool parameters
  177. tool["tool_parameters"] = masked_parameter
  178. except Exception as e:
  179. pass
  180. # override agent mode
  181. model_config.agent_mode = json.dumps(agent_mode)
  182. class ModifiedApp(App):
  183. """
  184. Modified App class
  185. """
  186. def __init__(self, app):
  187. self.__dict__.update(app.__dict__)
  188. @property
  189. def app_model_config(self):
  190. return model_config
  191. app = ModifiedApp(app)
  192. return app
  193. def update_app(self, app: App, args: dict) -> App:
  194. """
  195. Update app
  196. :param app: App instance
  197. :param args: request args
  198. :return: App instance
  199. """
  200. app.name = args.get("name")
  201. app.description = args.get("description", "")
  202. app.icon_type = args.get("icon_type", "emoji")
  203. app.icon = args.get("icon")
  204. app.icon_background = args.get("icon_background")
  205. app.use_icon_as_answer_icon = args.get("use_icon_as_answer_icon", False)
  206. app.updated_by = current_user.id
  207. app.updated_at = datetime.now(UTC).replace(tzinfo=None)
  208. db.session.commit()
  209. return app
  210. def update_app_name(self, app: App, name: str) -> App:
  211. """
  212. Update app name
  213. :param app: App instance
  214. :param name: new name
  215. :return: App instance
  216. """
  217. app.name = name
  218. app.updated_by = current_user.id
  219. app.updated_at = datetime.now(UTC).replace(tzinfo=None)
  220. db.session.commit()
  221. return app
  222. def update_app_icon(self, app: App, icon: str, icon_background: str) -> App:
  223. """
  224. Update app icon
  225. :param app: App instance
  226. :param icon: new icon
  227. :param icon_background: new icon_background
  228. :return: App instance
  229. """
  230. app.icon = icon
  231. app.icon_background = icon_background
  232. app.updated_by = current_user.id
  233. app.updated_at = datetime.now(UTC).replace(tzinfo=None)
  234. db.session.commit()
  235. return app
  236. def update_app_site_status(self, app: App, enable_site: bool) -> App:
  237. """
  238. Update app site status
  239. :param app: App instance
  240. :param enable_site: enable site status
  241. :return: App instance
  242. """
  243. if enable_site == app.enable_site:
  244. return app
  245. app.enable_site = enable_site
  246. app.updated_by = current_user.id
  247. app.updated_at = datetime.now(UTC).replace(tzinfo=None)
  248. db.session.commit()
  249. return app
  250. def update_app_api_status(self, app: App, enable_api: bool) -> App:
  251. """
  252. Update app api status
  253. :param app: App instance
  254. :param enable_api: enable api status
  255. :return: App instance
  256. """
  257. if enable_api == app.enable_api:
  258. return app
  259. app.enable_api = enable_api
  260. app.updated_by = current_user.id
  261. app.updated_at = datetime.now(UTC).replace(tzinfo=None)
  262. db.session.commit()
  263. return app
  264. def delete_app(self, app: App) -> None:
  265. """
  266. Delete app
  267. :param app: App instance
  268. """
  269. db.session.delete(app)
  270. db.session.commit()
  271. # clean up web app settings
  272. if FeatureService.get_system_features().webapp_auth.enabled:
  273. EnterpriseService.WebAppAuth.cleanup_webapp(app.id)
  274. # Trigger asynchronous deletion of app and related data
  275. remove_app_and_related_data_task.delay(tenant_id=app.tenant_id, app_id=app.id)
  276. def get_app_meta(self, app_model: App) -> dict:
  277. """
  278. Get app meta info
  279. :param app_model: app model
  280. :return:
  281. """
  282. app_mode = AppMode.value_of(app_model.mode)
  283. meta: dict = {"tool_icons": {}}
  284. if app_mode in {AppMode.ADVANCED_CHAT, AppMode.WORKFLOW}:
  285. workflow = app_model.workflow
  286. if workflow is None:
  287. return meta
  288. graph = workflow.graph_dict
  289. nodes = graph.get("nodes", [])
  290. tools = []
  291. for node in nodes:
  292. if node.get("data", {}).get("type") == "tool":
  293. node_data = node.get("data", {})
  294. tools.append(
  295. {
  296. "provider_type": node_data.get("provider_type"),
  297. "provider_id": node_data.get("provider_id"),
  298. "tool_name": node_data.get("tool_name"),
  299. "tool_parameters": {},
  300. }
  301. )
  302. else:
  303. app_model_config: Optional[AppModelConfig] = app_model.app_model_config
  304. if not app_model_config:
  305. return meta
  306. agent_config = app_model_config.agent_mode_dict
  307. # get all tools
  308. tools = agent_config.get("tools", [])
  309. url_prefix = dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/builtin/"
  310. for tool in tools:
  311. keys = list(tool.keys())
  312. if len(keys) >= 4:
  313. # current tool standard
  314. provider_type = tool.get("provider_type", "")
  315. provider_id = tool.get("provider_id", "")
  316. tool_name = tool.get("tool_name", "")
  317. if provider_type == "builtin":
  318. meta["tool_icons"][tool_name] = url_prefix + provider_id + "/icon"
  319. elif provider_type == "api":
  320. try:
  321. provider: Optional[ApiToolProvider] = (
  322. db.session.query(ApiToolProvider).filter(ApiToolProvider.id == provider_id).first()
  323. )
  324. if provider is None:
  325. raise ValueError(f"provider not found for tool {tool_name}")
  326. meta["tool_icons"][tool_name] = json.loads(provider.icon)
  327. except:
  328. meta["tool_icons"][tool_name] = {"background": "#252525", "content": "\ud83d\ude01"}
  329. return meta
  330. @staticmethod
  331. def get_app_code_by_id(app_id: str) -> str:
  332. """
  333. Get app code by app id
  334. :param app_id: app id
  335. :return: app code
  336. """
  337. site = db.session.query(Site).filter(Site.app_id == app_id).first()
  338. if not site:
  339. raise ValueError(f"App with id {app_id} not found")
  340. return str(site.code)
  341. @staticmethod
  342. def get_app_id_by_code(app_code: str) -> str:
  343. """
  344. Get app id by app code
  345. :param app_code: app code
  346. :return: app id
  347. """
  348. site = db.session.query(Site).filter(Site.code == app_code).first()
  349. if not site:
  350. raise ValueError(f"App with code {app_code} not found")
  351. return str(site.app_id)