fix(api): correct service api rate limit descriptions (#41975)

This commit is contained in:
Stephen Zhou 2026-09-08 09:33:46 +00:00 committed by GitHub
parent f3e5f41a3c
commit 2f00e647ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 90 additions and 19 deletions

View File

@ -353,7 +353,7 @@ class ChatApi(Resource):
404: "`not_found` : Conversation does not exist.",
429: (
"- `too_many_requests` : Too many concurrent requests for this app.\n"
"- `rate_limit_error` : The upstream model provider rate limit was exceeded."
"- `rate_limit_error` : The Dify Cloud workflow execution quota for this workspace has been reached."
),
500: "`internal_server_error` : Internal server error.",
},

View File

@ -315,8 +315,7 @@ class WorkflowRunApi(Resource):
),
429: (
"- `too_many_requests` : Too many concurrent requests for this app.\n"
"- `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution "
"quota was exceeded."
"- `rate_limit_error` : The Dify Cloud workflow execution quota for this workspace has been reached."
),
500: "`internal_server_error` : Internal server error.",
},
@ -430,8 +429,7 @@ class WorkflowRunByIdApi(Resource):
404: "`not_found` : Workflow not found.",
429: (
"- `too_many_requests` : Too many concurrent requests for this app.\n"
"- `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution "
"quota was exceeded."
"- `rate_limit_error` : The Dify Cloud workflow execution quota for this workspace has been reached."
),
500: "`internal_server_error` : Internal server error.",
},

View File

@ -338,7 +338,7 @@ Send a request to the chat application.
| 401 | Unauthorized - invalid API token | |
| 403 | `workflow_version_execution_not_allowed` : Workflow version execution is unavailable on the current plan. Upgrade to a paid plan. | |
| 404 | `not_found` : Conversation does not exist. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit was exceeded. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The Dify Cloud workflow execution quota for this workspace has been reached. | |
| 500 | `internal_server_error` : Internal server error. | |
### [POST] /chat-messages/{task_id}/stop
@ -485,7 +485,7 @@ Send a request to the chat application.
| 401 | Unauthorized - invalid API token | |
| 403 | `workflow_version_execution_not_allowed` : Workflow version execution is unavailable on the current plan. Upgrade to a paid plan. | |
| 404 | `not_found` : Conversation does not exist. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit was exceeded. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The Dify Cloud workflow execution quota for this workspace has been reached. | |
| 500 | `internal_server_error` : Internal server error. | |
### [POST] /chat-messages/{task_id}/stop
@ -2207,7 +2207,7 @@ Execute a workflow. Cannot be executed without a published workflow.
| 400 | - `not_workflow_app` : App mode does not match the API route. - `provider_not_initialize` : No valid model provider credentials found. - `provider_quota_exceeded` : Model provider quota exhausted. - `model_currently_not_support` : Current model unavailable. - `completion_request_error` : Workflow execution request failed. - `invalid_param` : Invalid parameter value. | |
| 401 | Unauthorized - invalid API token | |
| 403 | - `forbidden` : Token scope, app, or workspace access denied. - `trigger_workflow_service_mode_unavailable` : Trigger-entry workflows cannot be invoked through Web App, Service API, OpenAPI, or MCP. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution quota was exceeded. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The Dify Cloud workflow execution quota for this workspace has been reached. | |
| 500 | `internal_server_error` : Internal server error. | |
### [GET] /workflows/run/{workflow_run_id}
@ -2283,7 +2283,7 @@ Execute a specific workflow version identified by its ID. Useful for running a p
| 401 | Unauthorized - invalid API token | |
| 403 | - `forbidden` : Token scope, app, or workspace access denied. - `workflow_version_execution_not_allowed` : Workflow version execution is unavailable on the current plan. Upgrade to a paid plan. - `trigger_workflow_service_mode_unavailable` : The selected workflow version uses a trigger entry and cannot be invoked through Web App, Service API, OpenAPI, or MCP. | |
| 404 | `not_found` : Workflow not found. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The upstream model provider rate limit or the Dify Cloud workflow execution quota was exceeded. | |
| 429 | - `too_many_requests` : Too many concurrent requests for this app. - `rate_limit_error` : The Dify Cloud workflow execution quota for this workspace has been reached. | |
| 500 | `internal_server_error` : Internal server error. | |
---

View File

@ -36,14 +36,17 @@ from controllers.service_api.app.completion import (
from controllers.service_api.app.error import (
AgentNotPublishedError,
AppUnavailableError,
CompletionRequestError,
ConversationCompletedError,
NotChatAppError,
WorkflowVersionExecutionNotAllowedError,
)
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
from core.app.apps.agent_app.errors import AgentAppNotPublishedError
from core.errors.error import QuotaExceededError
from enums import CloudPlan, DeploymentEdition
from graphon.model_runtime.errors.invoke import InvokeError
from graphon.model_runtime.errors.invoke import InvokeRateLimitError as ProviderInvokeRateLimitError
from models.base import TypeBase
from models.enums import ConversationFromSource, EndUserType
from models.model import App, AppMode, Conversation, EndUser, IconType, Message
@ -553,6 +556,43 @@ class TestCompletionStopApiController:
class TestChatApiController:
@pytest.mark.parametrize(
("source_error", "http_error", "status_code", "error_code"),
[
pytest.param(InvokeRateLimitError, InvokeRateLimitHttpError, 429, "rate_limit_error", id="cloud-quota"),
pytest.param(
ProviderInvokeRateLimitError, CompletionRequestError, 400, "completion_request_error", id="provider"
),
],
)
def test_maps_rate_limits_by_source(
self,
app: Flask,
monkeypatch: pytest.MonkeyPatch,
orm_session: Session,
source_error: type[InvokeRateLimitError | ProviderInvokeRateLimitError],
http_error: type[InvokeRateLimitHttpError | CompletionRequestError],
status_code: int,
error_code: str,
) -> None:
generate = Mock(side_effect=source_error("rate limit reached"))
monkeypatch.setattr(AppGenerateService, "generate", generate)
app_model, end_user, _, _ = _persist_completion_state(orm_session, AppMode.ADVANCED_CHAT)
api = ChatApi()
handler = unwrap(api.post)
with app.test_request_context(
"/chat-messages", method="POST", json={"inputs": {}, "query": "hi", "response_mode": "blocking"}
):
with pytest.raises(http_error) as exc_info:
handler(api, session=orm_session, app_model=app_model, end_user=end_user)
generate.assert_called_once()
assert exc_info.value.code == status_code
assert exc_info.value.error_code == error_code
assert exc_info.value.description == "rate limit reached"
def test_rejects_sandbox_plan_workflow_version(
self, app: Flask, monkeypatch: pytest.MonkeyPatch, orm_session: Session
) -> None:

View File

@ -29,6 +29,7 @@ from sqlalchemy.orm import Session, sessionmaker
from werkzeug.exceptions import BadRequest, NotFound
from controllers.service_api.app.error import (
CompletionRequestError,
NotWorkflowAppError,
TriggerWorkflowServiceModeUnavailableError,
WorkflowVersionExecutionNotAllowedError,
@ -49,6 +50,7 @@ from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpErr
from core.app.entities.app_invoke_entities import InvokeFrom
from enums import CloudPlan, DeploymentEdition
from graphon.enums import WorkflowExecutionStatus
from graphon.model_runtime.errors.invoke import InvokeRateLimitError as ProviderInvokeRateLimitError
from models import Account
from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom
from models.model import App, AppMode, EndUser
@ -556,21 +558,51 @@ class TestWorkflowRunApi:
handler(api, session=sqlite_session, app_model=app_model, end_user=end_user)
@pytest.mark.parametrize("sqlite_session", [()], indirect=True)
def test_rate_limit(self, app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> None:
@pytest.mark.parametrize("api_class", [WorkflowRunApi, WorkflowRunByIdApi])
@pytest.mark.parametrize(
("source_error", "http_error", "status_code", "error_code"),
[
pytest.param(InvokeRateLimitError, InvokeRateLimitHttpError, 429, "rate_limit_error", id="cloud-quota"),
pytest.param(
ProviderInvokeRateLimitError, CompletionRequestError, 400, "completion_request_error", id="provider"
),
],
)
def test_maps_rate_limits_by_source(
self,
app: Flask,
monkeypatch: pytest.MonkeyPatch,
sqlite_session: Session,
config_overrides: Callable[..., None],
api_class: type[WorkflowRunApi | WorkflowRunByIdApi],
source_error: type[InvokeRateLimitError | ProviderInvokeRateLimitError],
http_error: type[InvokeRateLimitHttpError | CompletionRequestError],
status_code: int,
error_code: str,
) -> None:
config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.CLOUD)
monkeypatch.setattr(
AppGenerateService,
"generate",
lambda *_args, **_kwargs: (_ for _ in ()).throw(InvokeRateLimitError("slow")),
BillingService, "get_info", Mock(return_value={"subscription": {"plan": CloudPlan.PROFESSIONAL}})
)
generate = Mock(side_effect=source_error("rate limit reached"))
monkeypatch.setattr(AppGenerateService, "generate", generate)
api = WorkflowRunApi()
api = api_class()
handler = unwrap(api.post)
app_model = _make_app_model()
end_user = _make_end_user()
kwargs: dict[str, str] = {"workflow_id": str(uuid.uuid4())} if isinstance(api, WorkflowRunByIdApi) else {}
with app.test_request_context("/workflows/run", method="POST", json={"inputs": {}}):
with pytest.raises(InvokeRateLimitHttpError):
handler(api, session=sqlite_session, app_model=app_model, end_user=end_user)
with app.test_request_context(
"/workflows/run", method="POST", json={"inputs": {}, "response_mode": "blocking"}
):
with pytest.raises(http_error) as exc_info:
handler(api, session=sqlite_session, app_model=app_model, end_user=end_user, **kwargs)
generate.assert_called_once()
assert exc_info.value.code == status_code
assert exc_info.value.error_code == error_code
assert exc_info.value.description == "rate limit reached"
def test_trigger_workflow_returns_stable_unavailable_error(
self,

View File

@ -298,9 +298,10 @@ def test_service_openapi_documents_decorator_user_contracts():
assert schema["properties"]["user"] == USER_PROPERTY_SCHEMA
assert "user" in schema["required"]
for path in ("/workflows/run", "/workflows/{workflow_id}/run"):
for path in ("/chat-messages", "/workflows/run", "/workflows/{workflow_id}/run"):
rate_limit_description = paths[path]["post"]["responses"]["429"]["description"]
assert "upstream model provider rate limit" in rate_limit_description
assert "upstream model provider rate limit" not in rate_limit_description
assert "too_many_requests" in rate_limit_description
assert "Dify Cloud workflow execution quota" in rate_limit_description
task_stop_user_descriptions = {