54 lines
1.9 KiB
Python
54 lines
1.9 KiB
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import time
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LoopRateLimiter:
|
|||
|
|
"""循环速率限制:检测持续回显循环并抑制对话。
|
|||
|
|
|
|||
|
|
当同一会话在短时间内反复收到相同内容的消息时,
|
|||
|
|
判定为回显循环,对 conversation 进行抑制。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(self, max_loops: int = 5, window_s: float = 30.0, cooldown_s: float = 60.0):
|
|||
|
|
self._max_loops = max_loops
|
|||
|
|
self._window_s = window_s
|
|||
|
|
self._cooldown_s = cooldown_s
|
|||
|
|
self._counters: dict[str, list[float]] = {}
|
|||
|
|
self._suppressed: dict[str, float] = {}
|
|||
|
|
|
|||
|
|
def check(self, conversation_key: str, content: str) -> bool:
|
|||
|
|
"""返回 True 表示应该抑制此消息。"""
|
|||
|
|
|
|||
|
|
if conversation_key in self._suppressed:
|
|||
|
|
suppressed_at = self._suppressed[conversation_key]
|
|||
|
|
if time.monotonic() - suppressed_at < self._cooldown_s:
|
|||
|
|
return True
|
|||
|
|
del self._suppressed[conversation_key]
|
|||
|
|
|
|||
|
|
now = time.monotonic()
|
|||
|
|
if conversation_key not in self._counters:
|
|||
|
|
self._counters[conversation_key] = []
|
|||
|
|
timestamps = self._counters[conversation_key]
|
|||
|
|
|
|||
|
|
timestamps.append(now)
|
|||
|
|
timestamps[:] = [t for t in timestamps if now - t <= self._window_s]
|
|||
|
|
|
|||
|
|
if len(timestamps) >= self._max_loops:
|
|||
|
|
self._suppressed[conversation_key] = now
|
|||
|
|
self._counters.pop(conversation_key, None)
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def is_suppressed(self, conversation_key: str) -> bool:
|
|||
|
|
if conversation_key in self._suppressed:
|
|||
|
|
if time.monotonic() - self._suppressed[conversation_key] < self._cooldown_s:
|
|||
|
|
return True
|
|||
|
|
del self._suppressed[conversation_key]
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def reset(self, conversation_key: str) -> None:
|
|||
|
|
self._counters.pop(conversation_key, None)
|
|||
|
|
self._suppressed.pop(conversation_key, None)
|