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.

app_generate_service.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import uuid
  2. from collections.abc import Generator, Mapping
  3. from typing import Any, Optional, Union
  4. from openai._exceptions import RateLimitError
  5. from configs import dify_config
  6. from core.app.apps.advanced_chat.app_generator import AdvancedChatAppGenerator
  7. from core.app.apps.agent_chat.app_generator import AgentChatAppGenerator
  8. from core.app.apps.chat.app_generator import ChatAppGenerator
  9. from core.app.apps.completion.app_generator import CompletionAppGenerator
  10. from core.app.apps.workflow.app_generator import WorkflowAppGenerator
  11. from core.app.entities.app_invoke_entities import InvokeFrom
  12. from core.app.features.rate_limiting import RateLimit
  13. from libs.helper import RateLimiter
  14. from models.model import Account, App, AppMode, EndUser
  15. from models.workflow import Workflow
  16. from services.billing_service import BillingService
  17. from services.errors.app import WorkflowIdFormatError, WorkflowNotFoundError
  18. from services.errors.llm import InvokeRateLimitError
  19. from services.workflow_service import WorkflowService
  20. class AppGenerateService:
  21. system_rate_limiter = RateLimiter("app_daily_rate_limiter", dify_config.APP_DAILY_RATE_LIMIT, 86400)
  22. @classmethod
  23. def generate(
  24. cls,
  25. app_model: App,
  26. user: Union[Account, EndUser],
  27. args: Mapping[str, Any],
  28. invoke_from: InvokeFrom,
  29. streaming: bool = True,
  30. ):
  31. """
  32. App Content Generate
  33. :param app_model: app model
  34. :param user: user
  35. :param args: args
  36. :param invoke_from: invoke from
  37. :param streaming: streaming
  38. :return:
  39. """
  40. # system level rate limiter
  41. if dify_config.BILLING_ENABLED:
  42. # check if it's free plan
  43. limit_info = BillingService.get_info(app_model.tenant_id)
  44. if limit_info["subscription"]["plan"] == "sandbox":
  45. if cls.system_rate_limiter.is_rate_limited(app_model.tenant_id):
  46. raise InvokeRateLimitError(
  47. "Rate limit exceeded, please upgrade your plan "
  48. f"or your RPD was {dify_config.APP_DAILY_RATE_LIMIT} requests/day"
  49. )
  50. cls.system_rate_limiter.increment_rate_limit(app_model.tenant_id)
  51. # app level rate limiter
  52. max_active_request = cls._get_max_active_requests(app_model)
  53. rate_limit = RateLimit(app_model.id, max_active_request)
  54. request_id = RateLimit.gen_request_key()
  55. try:
  56. request_id = rate_limit.enter(request_id)
  57. if app_model.mode == AppMode.COMPLETION.value:
  58. return rate_limit.generate(
  59. CompletionAppGenerator.convert_to_event_stream(
  60. CompletionAppGenerator().generate(
  61. app_model=app_model, user=user, args=args, invoke_from=invoke_from, streaming=streaming
  62. ),
  63. ),
  64. request_id=request_id,
  65. )
  66. elif app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent:
  67. return rate_limit.generate(
  68. AgentChatAppGenerator.convert_to_event_stream(
  69. AgentChatAppGenerator().generate(
  70. app_model=app_model, user=user, args=args, invoke_from=invoke_from, streaming=streaming
  71. ),
  72. ),
  73. request_id,
  74. )
  75. elif app_model.mode == AppMode.CHAT.value:
  76. return rate_limit.generate(
  77. ChatAppGenerator.convert_to_event_stream(
  78. ChatAppGenerator().generate(
  79. app_model=app_model, user=user, args=args, invoke_from=invoke_from, streaming=streaming
  80. ),
  81. ),
  82. request_id=request_id,
  83. )
  84. elif app_model.mode == AppMode.ADVANCED_CHAT.value:
  85. workflow_id = args.get("workflow_id")
  86. workflow = cls._get_workflow(app_model, invoke_from, workflow_id)
  87. return rate_limit.generate(
  88. AdvancedChatAppGenerator.convert_to_event_stream(
  89. AdvancedChatAppGenerator().generate(
  90. app_model=app_model,
  91. workflow=workflow,
  92. user=user,
  93. args=args,
  94. invoke_from=invoke_from,
  95. streaming=streaming,
  96. ),
  97. ),
  98. request_id=request_id,
  99. )
  100. elif app_model.mode == AppMode.WORKFLOW.value:
  101. workflow_id = args.get("workflow_id")
  102. workflow = cls._get_workflow(app_model, invoke_from, workflow_id)
  103. return rate_limit.generate(
  104. WorkflowAppGenerator.convert_to_event_stream(
  105. WorkflowAppGenerator().generate(
  106. app_model=app_model,
  107. workflow=workflow,
  108. user=user,
  109. args=args,
  110. invoke_from=invoke_from,
  111. streaming=streaming,
  112. call_depth=0,
  113. workflow_thread_pool_id=None,
  114. ),
  115. ),
  116. request_id,
  117. )
  118. else:
  119. raise ValueError(f"Invalid app mode {app_model.mode}")
  120. except RateLimitError as e:
  121. raise InvokeRateLimitError(str(e))
  122. except Exception:
  123. rate_limit.exit(request_id)
  124. raise
  125. finally:
  126. if not streaming:
  127. rate_limit.exit(request_id)
  128. @staticmethod
  129. def _get_max_active_requests(app: App) -> int:
  130. """
  131. Get the maximum number of active requests allowed for an app.
  132. Returns the smaller value between app's custom limit and global config limit.
  133. A value of 0 means infinite (no limit).
  134. Args:
  135. app: The App model instance
  136. Returns:
  137. The maximum number of active requests allowed
  138. """
  139. app_limit = app.max_active_requests or 0
  140. config_limit = dify_config.APP_MAX_ACTIVE_REQUESTS
  141. # Filter out infinite (0) values and return the minimum, or 0 if both are infinite
  142. limits = [limit for limit in [app_limit, config_limit] if limit > 0]
  143. return min(limits) if limits else 0
  144. @classmethod
  145. def generate_single_iteration(cls, app_model: App, user: Account, node_id: str, args: Any, streaming: bool = True):
  146. if app_model.mode == AppMode.ADVANCED_CHAT.value:
  147. workflow = cls._get_workflow(app_model, InvokeFrom.DEBUGGER)
  148. return AdvancedChatAppGenerator.convert_to_event_stream(
  149. AdvancedChatAppGenerator().single_iteration_generate(
  150. app_model=app_model, workflow=workflow, node_id=node_id, user=user, args=args, streaming=streaming
  151. )
  152. )
  153. elif app_model.mode == AppMode.WORKFLOW.value:
  154. workflow = cls._get_workflow(app_model, InvokeFrom.DEBUGGER)
  155. return AdvancedChatAppGenerator.convert_to_event_stream(
  156. WorkflowAppGenerator().single_iteration_generate(
  157. app_model=app_model, workflow=workflow, node_id=node_id, user=user, args=args, streaming=streaming
  158. )
  159. )
  160. else:
  161. raise ValueError(f"Invalid app mode {app_model.mode}")
  162. @classmethod
  163. def generate_single_loop(cls, app_model: App, user: Account, node_id: str, args: Any, streaming: bool = True):
  164. if app_model.mode == AppMode.ADVANCED_CHAT.value:
  165. workflow = cls._get_workflow(app_model, InvokeFrom.DEBUGGER)
  166. return AdvancedChatAppGenerator.convert_to_event_stream(
  167. AdvancedChatAppGenerator().single_loop_generate(
  168. app_model=app_model, workflow=workflow, node_id=node_id, user=user, args=args, streaming=streaming
  169. )
  170. )
  171. elif app_model.mode == AppMode.WORKFLOW.value:
  172. workflow = cls._get_workflow(app_model, InvokeFrom.DEBUGGER)
  173. return AdvancedChatAppGenerator.convert_to_event_stream(
  174. WorkflowAppGenerator().single_loop_generate(
  175. app_model=app_model, workflow=workflow, node_id=node_id, user=user, args=args, streaming=streaming
  176. )
  177. )
  178. else:
  179. raise ValueError(f"Invalid app mode {app_model.mode}")
  180. @classmethod
  181. def generate_more_like_this(
  182. cls,
  183. app_model: App,
  184. user: Union[Account, EndUser],
  185. message_id: str,
  186. invoke_from: InvokeFrom,
  187. streaming: bool = True,
  188. ) -> Union[Mapping, Generator]:
  189. """
  190. Generate more like this
  191. :param app_model: app model
  192. :param user: user
  193. :param message_id: message id
  194. :param invoke_from: invoke from
  195. :param streaming: streaming
  196. :return:
  197. """
  198. return CompletionAppGenerator().generate_more_like_this(
  199. app_model=app_model, message_id=message_id, user=user, invoke_from=invoke_from, stream=streaming
  200. )
  201. @classmethod
  202. def _get_workflow(cls, app_model: App, invoke_from: InvokeFrom, workflow_id: Optional[str] = None) -> Workflow:
  203. """
  204. Get workflow
  205. :param app_model: app model
  206. :param invoke_from: invoke from
  207. :param workflow_id: optional workflow id to specify a specific version
  208. :return:
  209. """
  210. workflow_service = WorkflowService()
  211. # If workflow_id is specified, get the specific workflow version
  212. if workflow_id:
  213. try:
  214. _ = uuid.UUID(workflow_id)
  215. except ValueError:
  216. raise WorkflowIdFormatError(f"Invalid workflow_id format: '{workflow_id}'. ")
  217. workflow = workflow_service.get_published_workflow_by_id(app_model=app_model, workflow_id=workflow_id)
  218. if not workflow:
  219. raise WorkflowNotFoundError(f"Workflow not found with id: {workflow_id}")
  220. return workflow
  221. if invoke_from == InvokeFrom.DEBUGGER:
  222. # fetch draft workflow by app_model
  223. workflow = workflow_service.get_draft_workflow(app_model=app_model)
  224. if not workflow:
  225. raise ValueError("Workflow not initialized")
  226. else:
  227. # fetch published workflow by app_model
  228. workflow = workflow_service.get_published_workflow(app_model=app_model)
  229. if not workflow:
  230. raise ValueError("Workflow not published")
  231. return workflow