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

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