Add app-level Redis-based RateLimiter (#4336)

This commit is contained in:
Stanislav Novosad
2025-12-18 18:16:09 -07:00
committed by GitHub
parent e84f628cf2
commit b3e8a59e2d
6 changed files with 65 additions and 3 deletions

View File

@@ -0,0 +1,33 @@
from typing import Protocol
class RateLimiter(Protocol):
"""
Protocol for rate limiting submit run requests per organization.
Implementations should be thread-safe and work correctly in distributed environments.
"""
async def rate_limit_submit_run(self, organization_id: str) -> None:
"""
Check and enforce rate limit for submitting a new run (task/workflow)
raises RateLimitExceeded exception if rate limit is exceeded.
Args:
organization_id: The organization ID to rate limit
Raises:
Exception: If rate limit is exceeded (implementation-specific exception)
"""
...
class NoopRateLimiter(RateLimiter):
"""
No-op rate limiter.
This implementation does not enforce any rate limits.
"""
async def rate_limit_submit_run(self, organization_id: str) -> None:
"""No-op implementation that never rate limits."""

View File

@@ -162,6 +162,7 @@ async def run_task(
) -> TaskRunResponse:
analytics.capture("skyvern-oss-run-task", data={"url": run_request.url})
await PermissionCheckerFactory.get_instance().check(current_org, browser_session_id=run_request.browser_session_id)
await app.RATE_LIMITER.rate_limit_submit_run(current_org.organization_id)
if run_request.engine in CUA_ENGINES or run_request.engine == RunEngine.skyvern_v1:
# create task v1
@@ -347,6 +348,7 @@ async def run_workflow(
await PermissionCheckerFactory.get_instance().check(
current_org, browser_session_id=workflow_run_request.browser_session_id
)
await app.RATE_LIMITER.rate_limit_submit_run(current_org.organization_id)
workflow_id = workflow_run_request.workflow_id
context = skyvern_context.ensure_context()
request_id = context.request_id
@@ -1623,6 +1625,7 @@ async def run_task_v1(
) -> CreateTaskResponse:
analytics.capture("skyvern-oss-agent-task-create", data={"url": task.url})
await PermissionCheckerFactory.get_instance().check(current_org, browser_session_id=task.browser_session_id)
await app.RATE_LIMITER.rate_limit_submit_run(current_org.organization_id)
created_task = await task_v1_service.run_task(
task=task,
@@ -2086,6 +2089,7 @@ async def run_workflow_legacy(
current_org,
browser_session_id=workflow_request.browser_session_id,
)
await app.RATE_LIMITER.rate_limit_submit_run(current_org.organization_id)
try:
workflow_run = await workflow_service.run_workflow(
@@ -2667,6 +2671,7 @@ async def run_task_v2(
max_steps_override=x_max_steps_override,
)
await PermissionCheckerFactory.get_instance().check(organization, browser_session_id=data.browser_session_id)
await app.RATE_LIMITER.rate_limit_submit_run(organization.organization_id)
try:
task_v2 = await task_v2_service.initialize_task_v2(