refactor ai_auto_response plugin architecture

This commit is contained in:
liuwei
2026-04-09 17:46:30 +08:00
parent cc65378544
commit f580c69736
39 changed files with 4347 additions and 1979 deletions

View File

@@ -0,0 +1,19 @@
from __future__ import annotations
from .dedup import DedupManager
from .filters import (
is_coding_work_request,
is_prompt_attack,
is_targeting_other_user,
should_ignore,
strip_at_prefix,
)
__all__ = [
"DedupManager",
"is_coding_work_request",
"is_prompt_attack",
"is_targeting_other_user",
"should_ignore",
"strip_at_prefix",
]

View File

@@ -0,0 +1,53 @@
from __future__ import annotations
import time
from typing import Dict, Set
class DedupManager:
def __init__(self):
self.inflight_message_keys: Set[str] = set()
self.recent_message_keys: Dict[str, float] = {}
self.recent_reply_signatures: Dict[str, float] = {}
def begin_message_processing(self, message_key: str, expiry_sec: int) -> bool:
if not message_key:
return True
now = time.time()
stale_keys = [key for key, ts in self.recent_message_keys.items() if now - ts > expiry_sec]
for key in stale_keys:
self.recent_message_keys.pop(key, None)
if message_key in self.inflight_message_keys:
return False
if message_key in self.recent_message_keys:
return False
self.inflight_message_keys.add(message_key)
return True
def finish_message_processing(self, message_key: str) -> None:
if not message_key:
return
self.inflight_message_keys.discard(message_key)
self.recent_message_keys[message_key] = time.time()
def should_skip_duplicate_reply(
self,
*,
room_id: str,
sender: str,
reply_text: str,
expiry_sec: int,
scope: str = "sender",
) -> bool:
text = str(reply_text or "").strip()
if not text:
return False
now = time.time()
stale_keys = [key for key, ts in self.recent_reply_signatures.items() if now - ts > expiry_sec]
for key in stale_keys:
self.recent_reply_signatures.pop(key, None)
signature = f"{room_id}:{text}" if scope == "room" else f"{room_id}:{sender}:{text}"
if signature in self.recent_reply_signatures:
return True
self.recent_reply_signatures[signature] = now
return False

View File

@@ -0,0 +1,66 @@
from __future__ import annotations
import re
from typing import Any, Dict
PROMPT_ATTACK_PATTERNS = [
r"(?i)\bprompt\b",
r"(?i)\bignore\b",
r"(?i)\bsystem\b",
r"(?i)\brole\b",
r"(?i)\bjailbreak\b",
r"(?i)提示词",
r"(?i)越狱",
r"(?i)扮演",
r"(?i)现在你是",
r"(?i)你是.+?(机器人|助手|模型|ai)",
r"(?i)忘记(之前|上面|所有|设定|规则)",
r"(?i)重置(设定|规则|系统|人格)",
]
CODING_WORK_PATTERNS = [
r"(?i)写(个|一段|一下|一份)?.{0,8}(代码|脚本|程序|插件|接口|爬虫|sql|配置)",
r"(?i)(帮我|给我|直接).{0,8}(写|做|实现|生成|改).{0,12}(代码|脚本|程序|插件|接口|sql|配置)",
r"(?i)(实现|开发|编写|重构|修改|修复).{0,16}(插件|代码|脚本|程序|接口|功能)",
r"(?i)(给我|帮我).{0,10}(搞个|整一个).{0,12}(机器人|插件|脚本|程序)",
r"(?i)\bdebug\b",
r"(?i)\bfix\b",
r"(?i)\brefactor\b",
r"(?i)\bimplement\b",
]
def strip_at_prefix(content: str) -> str:
return re.sub(r"@.*?[\u2005\s]+", "", str(content or "")).strip()
def should_ignore(content: str, filters: Dict[str, Any]) -> bool:
content = str(content or "").strip()
filters = filters or {}
if len(content) < int(filters.get("min_text_length", 1)):
return True
if content in set(filters.get("ignore_exact", [])):
return True
return any(content.startswith(prefix) for prefix in filters.get("ignore_prefixes", []))
def is_prompt_attack(content: str) -> bool:
text = str(content or "").strip()
if not text:
return False
return any(re.search(pattern, text) for pattern in PROMPT_ATTACK_PATTERNS)
def is_coding_work_request(content: str) -> bool:
text = str(content or "").strip()
if not text:
return False
return any(re.search(pattern, text) for pattern in CODING_WORK_PATTERNS)
def is_targeting_other_user(message: Dict[str, Any]) -> bool:
if message.get("is_at", False):
return False
raw_content = str(message.get("content", "") or "")
return "@" in raw_content