-
Notifications
You must be signed in to change notification settings - Fork 48.1k
fix(dingtalk): add AI Card QPS token-bucket throttle #17365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
spike2204
wants to merge
1
commit into
NousResearch:main
Choose a base branch
from
spike2204:fix/dingtalk-card-qps-throttle
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+42
−4
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
| import logging | ||
| import os | ||
| import re | ||
| import time | ||
| import traceback | ||
| import uuid | ||
| from datetime import datetime, timezone | ||
|
|
@@ -202,6 +203,10 @@ def __init__(self, config: PlatformConfig): | |
| # auto-close them as siblings — otherwise tool-progress cards get | ||
| # stuck in streaming state forever. | ||
| self._streaming_cards: Dict[str, Dict[str, str]] = {} | ||
| # Per-card last-edit timestamp (ms) | ||
| self._card_last_edit_ms: Dict[str, int] = {} | ||
| # Per-chat error-send cooldown | ||
| self._error_last_sent_ms: Dict[str, int] = {} | ||
| # Track fire-and-forget emoji/reaction coroutines so Python's GC | ||
| # doesn't drop them mid-flight, and we can cancel them on disconnect. | ||
| self._bg_tasks: Set[asyncio.Task] = set() | ||
|
|
@@ -338,6 +343,8 @@ async def disconnect(self) -> None: | |
| self._session_webhooks.clear() | ||
| self._message_contexts.clear() | ||
| self._streaming_cards.clear() | ||
| self._card_last_edit_ms.clear() | ||
| self._error_last_sent_ms.clear() | ||
| self._done_emoji_fired.clear() | ||
| self._dedup.clear() | ||
| logger.info("[%s] Disconnected", self.name) | ||
|
|
@@ -1020,6 +1027,19 @@ async def edit_message( | |
| """ | ||
| if not message_id: | ||
| return SendResult(success=False, error="message_id required") | ||
|
|
||
| # Throttle non-finalize edits per out_track_id. | ||
| now_ms = int(datetime.now(tz=timezone.utc).timestamp() * 1000) | ||
| if not finalize: | ||
| last_ms = self._card_last_edit_ms.get(message_id, 0) | ||
| if now_ms - last_ms < _CARD_EDIT_THROTTLE_MS: | ||
| logger.debug( | ||
| "[%s] edit_message throttled (%dms since last) for %s", | ||
| self.name, now_ms - last_ms, message_id, | ||
| ) | ||
| return SendResult(success=True, message_id=message_id) | ||
| self._card_last_edit_ms[message_id] = now_ms | ||
|
|
||
| token = await self._get_access_token() | ||
| if not token: | ||
| return SendResult(success=False, error="No access token") | ||
|
|
@@ -1035,6 +1055,7 @@ async def edit_message( | |
| self._streaming_cards.get(chat_id, {}).pop(message_id, None) | ||
| if not self._streaming_cards.get(chat_id): | ||
| self._streaming_cards.pop(chat_id, None) | ||
| self._card_last_edit_ms.pop(message_id, None) | ||
| logger.debug( | ||
| "[%s] AI Card finalized (edit): %s", | ||
| self.name, message_id, | ||
|
|
@@ -1057,7 +1078,13 @@ async def _stream_card_content( | |
| content: str, | ||
| finalize: bool = False, | ||
| ) -> None: | ||
| """Stream content to an existing AI Card.""" | ||
| """Stream content to an existing AI Card. | ||
|
|
||
| Per-card 800ms throttle happens at the ``edit_message`` layer; this | ||
| function additionally goes through the **global** token bucket so | ||
| that many parallel chats can't collectively overrun the tenant-wide | ||
| DingTalk card-API QPS cap (~40/s). | ||
| """ | ||
| stream_request = dingtalk_card_models.StreamingUpdateRequest( | ||
| out_track_id=out_track_id, | ||
| guid=str(uuid.uuid4()), | ||
|
|
@@ -1073,9 +1100,20 @@ async def _stream_card_content( | |
| ) | ||
|
|
||
| runtime = tea_util_models.RuntimeOptions() | ||
| await self._card_sdk.streaming_update_with_options_async( | ||
| stream_request, stream_headers, runtime | ||
| ) | ||
| await _CARD_BUCKET.acquire() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| try: | ||
| await self._card_sdk.streaming_update_with_options_async( | ||
| stream_request, stream_headers, runtime | ||
| ) | ||
| except Exception as e: | ||
| err_msg = str(e) | ||
| if "QpsLimit" in err_msg or "403" in err_msg or "qps" in err_msg.lower(): | ||
| logger.warning( | ||
| "[%s] Card QPS limit hit, backing off %dms: %s", | ||
| self.name, _CARD_API_QPS_BACKOFF_MS, err_msg[:160], | ||
| ) | ||
| _CARD_BUCKET.trigger_backoff() | ||
| raise | ||
|
|
||
| async def _get_access_token(self) -> Optional[str]: | ||
| """Get access token using SDK's cached token.""" | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_CARD_EDIT_THROTTLE_MSis not defined in this PR or its base. A non-final edit will raiseNameError; include the limiter constants and implementation in this self-contained PR.