Skip to content

Commit f121227

Browse files
Yeuolyparambharat
authored andcommitted
feat: tenant app invocations limiter (langgenius#16221)
1 parent ed4ab50 commit f121227

File tree

8 files changed

+68
-16
lines changed

8 files changed

+68
-16
lines changed

api/configs/feature/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ class AppExecutionConfig(BaseSettings):
6161
description="Maximum number of concurrent active requests per app (0 for unlimited)",
6262
default=0,
6363
)
64+
APP_DAILY_RATE_LIMIT: NonNegativeInt = Field(
65+
description="Maximum number of requests per app per day",
66+
default=5000,
67+
)
6468

6569

6670
class CodeExecutionSandboxConfig(BaseSettings):

api/controllers/console/app/workflow.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010
import services
1111
from configs import dify_config
1212
from controllers.console import api
13-
from controllers.console.app.error import ConversationCompletedError, DraftWorkflowNotExist, DraftWorkflowNotSync
13+
from controllers.console.app.error import (
14+
ConversationCompletedError,
15+
DraftWorkflowNotExist,
16+
DraftWorkflowNotSync,
17+
)
1418
from controllers.console.app.wraps import get_app_model
1519
from controllers.console.wraps import account_initialization_required, setup_required
20+
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
1621
from core.app.apps.base_app_queue_manager import AppQueueManager
1722
from core.app.entities.app_invoke_entities import InvokeFrom
1823
from extensions.ext_database import db
@@ -27,6 +32,7 @@
2732
from models.model import AppMode
2833
from services.app_generate_service import AppGenerateService
2934
from services.errors.app import WorkflowHashNotEqualError
35+
from services.errors.llm import InvokeRateLimitError
3036
from services.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError, WorkflowService
3137

3238
logger = logging.getLogger(__name__)
@@ -168,6 +174,8 @@ def post(self, app_model: App):
168174
raise NotFound("Conversation Not Exists.")
169175
except services.errors.conversation.ConversationCompletedError:
170176
raise ConversationCompletedError()
177+
except InvokeRateLimitError as ex:
178+
raise InvokeRateLimitHttpError(ex.description)
171179
except ValueError as e:
172180
raise e
173181
except Exception:
@@ -344,15 +352,18 @@ def post(self, app_model: App):
344352
parser.add_argument("files", type=list, required=False, location="json")
345353
args = parser.parse_args()
346354

347-
response = AppGenerateService.generate(
348-
app_model=app_model,
349-
user=current_user,
350-
args=args,
351-
invoke_from=InvokeFrom.DEBUGGER,
352-
streaming=True,
353-
)
355+
try:
356+
response = AppGenerateService.generate(
357+
app_model=app_model,
358+
user=current_user,
359+
args=args,
360+
invoke_from=InvokeFrom.DEBUGGER,
361+
streaming=True,
362+
)
354363

355-
return helper.compact_generate_response(response)
364+
return helper.compact_generate_response(response)
365+
except InvokeRateLimitError as ex:
366+
raise InvokeRateLimitHttpError(ex.description)
356367

357368

358369
class WorkflowTaskStopApi(Resource):

api/controllers/console/explore/completion.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
)
1717
from controllers.console.explore.error import NotChatAppError, NotCompletionAppError
1818
from controllers.console.explore.wraps import InstalledAppResource
19+
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
1920
from core.app.apps.base_app_queue_manager import AppQueueManager
2021
from core.app.entities.app_invoke_entities import InvokeFrom
2122
from core.errors.error import (
@@ -29,6 +30,7 @@
2930
from libs.helper import uuid_value
3031
from models.model import AppMode
3132
from services.app_generate_service import AppGenerateService
33+
from services.errors.llm import InvokeRateLimitError
3234

3335

3436
# define completion api for user
@@ -75,7 +77,7 @@ def post(self, installed_app):
7577
raise CompletionRequestError(e.description)
7678
except ValueError as e:
7779
raise e
78-
except Exception as e:
80+
except Exception:
7981
logging.exception("internal server error.")
8082
raise InternalServerError()
8183

@@ -133,9 +135,11 @@ def post(self, installed_app):
133135
raise ProviderModelCurrentlyNotSupportError()
134136
except InvokeError as e:
135137
raise CompletionRequestError(e.description)
138+
except InvokeRateLimitError as ex:
139+
raise InvokeRateLimitHttpError(ex.description)
136140
except ValueError as e:
137141
raise e
138-
except Exception as e:
142+
except Exception:
139143
logging.exception("internal server error.")
140144
raise InternalServerError()
141145

api/controllers/console/explore/workflow.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212
from controllers.console.explore.error import NotWorkflowAppError
1313
from controllers.console.explore.wraps import InstalledAppResource
14+
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
1415
from core.app.apps.base_app_queue_manager import AppQueueManager
1516
from core.app.entities.app_invoke_entities import InvokeFrom
1617
from core.errors.error import (
@@ -23,6 +24,7 @@
2324
from libs.login import current_user
2425
from models.model import AppMode, InstalledApp
2526
from services.app_generate_service import AppGenerateService
27+
from services.errors.llm import InvokeRateLimitError
2628

2729
logger = logging.getLogger(__name__)
2830

@@ -56,9 +58,11 @@ def post(self, installed_app: InstalledApp):
5658
raise ProviderModelCurrentlyNotSupportError()
5759
except InvokeError as e:
5860
raise CompletionRequestError(e.description)
61+
except InvokeRateLimitError as ex:
62+
raise InvokeRateLimitHttpError(ex.description)
5963
except ValueError as e:
6064
raise e
61-
except Exception as e:
65+
except Exception:
6266
logging.exception("internal server error.")
6367
raise InternalServerError()
6468

api/controllers/service_api/app/completion.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ProviderQuotaExceededError,
1616
)
1717
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
18+
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
1819
from core.app.apps.base_app_queue_manager import AppQueueManager
1920
from core.app.entities.app_invoke_entities import InvokeFrom
2021
from core.errors.error import (
@@ -27,6 +28,7 @@
2728
from libs.helper import uuid_value
2829
from models.model import App, AppMode, EndUser
2930
from services.app_generate_service import AppGenerateService
31+
from services.errors.llm import InvokeRateLimitError
3032

3133

3234
class CompletionApi(Resource):
@@ -75,7 +77,7 @@ def post(self, app_model: App, end_user: EndUser):
7577
raise CompletionRequestError(e.description)
7678
except ValueError as e:
7779
raise e
78-
except Exception as e:
80+
except Exception:
7981
logging.exception("internal server error.")
8082
raise InternalServerError()
8183

@@ -130,11 +132,13 @@ def post(self, app_model: App, end_user: EndUser):
130132
raise ProviderQuotaExceededError()
131133
except ModelCurrentlyNotSupportError:
132134
raise ProviderModelCurrentlyNotSupportError()
135+
except InvokeRateLimitError as ex:
136+
raise InvokeRateLimitHttpError(ex.description)
133137
except InvokeError as e:
134138
raise CompletionRequestError(e.description)
135139
except ValueError as e:
136140
raise e
137-
except Exception as e:
141+
except Exception:
138142
logging.exception("internal server error.")
139143
raise InternalServerError()
140144

api/controllers/service_api/app/workflow.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ProviderQuotaExceededError,
1616
)
1717
from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token
18+
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
1819
from core.app.apps.base_app_queue_manager import AppQueueManager
1920
from core.app.entities.app_invoke_entities import InvokeFrom
2021
from core.errors.error import (
@@ -29,6 +30,7 @@
2930
from models.model import App, AppMode, EndUser
3031
from models.workflow import WorkflowRun, WorkflowRunStatus
3132
from services.app_generate_service import AppGenerateService
33+
from services.errors.llm import InvokeRateLimitError
3234
from services.workflow_app_service import WorkflowAppService
3335

3436
logger = logging.getLogger(__name__)
@@ -93,11 +95,13 @@ def post(self, app_model: App, end_user: EndUser):
9395
raise ProviderQuotaExceededError()
9496
except ModelCurrentlyNotSupportError:
9597
raise ProviderModelCurrentlyNotSupportError()
98+
except InvokeRateLimitError as ex:
99+
raise InvokeRateLimitHttpError(ex.description)
96100
except InvokeError as e:
97101
raise CompletionRequestError(e.description)
98102
except ValueError as e:
99103
raise e
100-
except Exception as e:
104+
except Exception:
101105
logging.exception("internal server error.")
102106
raise InternalServerError()
103107

api/controllers/web/workflow.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
ProviderNotInitializeError,
1212
ProviderQuotaExceededError,
1313
)
14+
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
1415
from controllers.web.wraps import WebApiResource
1516
from core.app.apps.base_app_queue_manager import AppQueueManager
1617
from core.app.entities.app_invoke_entities import InvokeFrom
@@ -23,6 +24,7 @@
2324
from libs import helper
2425
from models.model import App, AppMode, EndUser
2526
from services.app_generate_service import AppGenerateService
27+
from services.errors.llm import InvokeRateLimitError
2628

2729
logger = logging.getLogger(__name__)
2830

@@ -55,9 +57,11 @@ def post(self, app_model: App, end_user: EndUser):
5557
raise ProviderModelCurrentlyNotSupportError()
5658
except InvokeError as e:
5759
raise CompletionRequestError(e.description)
60+
except InvokeRateLimitError as ex:
61+
raise InvokeRateLimitHttpError(ex.description)
5862
except ValueError as e:
5963
raise e
60-
except Exception as e:
64+
except Exception:
6165
logging.exception("internal server error.")
6266
raise InternalServerError()
6367

api/services/app_generate_service.py

+17
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@
1111
from core.app.apps.workflow.app_generator import WorkflowAppGenerator
1212
from core.app.entities.app_invoke_entities import InvokeFrom
1313
from core.app.features.rate_limiting import RateLimit
14+
from libs.helper import RateLimiter
1415
from models.model import Account, App, AppMode, EndUser
1516
from models.workflow import Workflow
17+
from services.billing_service import BillingService
1618
from services.errors.llm import InvokeRateLimitError
1719
from services.workflow_service import WorkflowService
1820

1921

2022
class AppGenerateService:
23+
system_rate_limiter = RateLimiter("app_daily_rate_limiter", dify_config.APP_DAILY_RATE_LIMIT, 86400)
24+
2125
@classmethod
2226
def generate(
2327
cls,
@@ -36,6 +40,19 @@ def generate(
3640
:param streaming: streaming
3741
:return:
3842
"""
43+
# system level rate limiter
44+
if dify_config.BILLING_ENABLED:
45+
# check if it's free plan
46+
limit_info = BillingService.get_info(app_model.tenant_id)
47+
if limit_info["subscription"]["plan"] == "sandbox":
48+
if cls.system_rate_limiter.is_rate_limited(app_model.tenant_id):
49+
raise InvokeRateLimitError(
50+
"Rate limit exceeded, please upgrade your plan "
51+
f"or your RPD was {dify_config.APP_DAILY_RATE_LIMIT} requests/day"
52+
)
53+
cls.system_rate_limiter.increment_rate_limit(app_model.tenant_id)
54+
55+
# app level rate limiter
3956
max_active_request = AppGenerateService._get_max_active_requests(app_model)
4057
rate_limit = RateLimit(app_model.id, max_active_request)
4158
request_id = RateLimit.gen_request_key()

0 commit comments

Comments
 (0)