From f593f5dd900f02c65f3db0eba293da349ec8d4df Mon Sep 17 00:00:00 2001 From: liuwei Date: Fri, 24 Apr 2026 14:44:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20ai=5Fauto=5Fresponse=20?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E4=BE=A7=E5=AE=9A=E5=90=91=E8=BE=B1=E9=AA=82?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加 directed abuse 场景识别,只作为模型输入信号,不做本地硬编码回复 - 在触发与规划层为定向挑衅场景单独标记,并强制走 social_short 短回复模式 - 将 abuse_directed 信号写入 Dify control,帮助模型稳定识别被点名挑衅场景 - 优化 Dify 主提示词与保守降级提示词,要求 abuse_directed 时默认短回且不要空掉 - 保持回复仍由模型生成,避免本地模板化回复暴露机器人痕迹 --- .../ai_auto_response/core/response_planner.py | 6 +++ plugins/ai_auto_response/core/triggers.py | 16 ++++++++ .../docs/小牛群内自动插话AI.yml | 10 +++++ plugins/ai_auto_response/main.py | 11 ++++++ plugins/ai_auto_response/safety/filters.py | 39 +++++++++++++++++++ 5 files changed, 82 insertions(+) diff --git a/plugins/ai_auto_response/core/response_planner.py b/plugins/ai_auto_response/core/response_planner.py index 6186b5f..cfe44dd 100644 --- a/plugins/ai_auto_response/core/response_planner.py +++ b/plugins/ai_auto_response/core/response_planner.py @@ -5,6 +5,10 @@ from typing import Dict class ResponsePlanner: def choose_reply_mode(self, trigger: Dict, flow_state: str) -> str: + # 被明确点名辱骂/挑衅时,最像真人的反应通常不是长解释, + # 而是一句短短的回怼或挡回去,所以这里强制走 social_short。 + if trigger.get("is_directed_abuse"): + return "social_short" if trigger.get("is_question"): return "qa_with_context" if flow_state in {"engaged", "deep_engaged"} else "qa_fast" if trigger.get("is_followup"): @@ -31,6 +35,8 @@ class ResponsePlanner: question_detected = bool(trigger.get("question_detected")) if trigger.get("is_at") or trigger_type == "at_trigger": return True + if trigger.get("is_directed_abuse") and directed: + return True if trigger_type == "quote_followup_trigger" and directed: return True if trigger.get("is_question") and conversation_hints.get("has_recent_human_solver") and flow_state == "idle": diff --git a/plugins/ai_auto_response/core/triggers.py b/plugins/ai_auto_response/core/triggers.py index f825ac8..896bff0 100644 --- a/plugins/ai_auto_response/core/triggers.py +++ b/plugins/ai_auto_response/core/triggers.py @@ -4,6 +4,8 @@ import re from dataclasses import dataclass, field from typing import Dict, List +from ..safety.filters import is_directed_abuse + QUESTION_PATTERNS = [ r"\?$", r"?$", r"怎么", r"如何", r"咋弄", r"为啥", r"为什么", @@ -27,6 +29,9 @@ class TriggerResult: # 以便阻止 topic/主动接话路径误把它当成 bot 可抢答的机会。 question_detected: bool = False is_directed: bool = False + # 这个标记表示“当前消息不是普通情绪话,而是在对 bot 进行定向挑衅/辱骂”。 + # 它的作用不是本地写死回复,而是让后续规划层优先走短句回怼场景。 + is_directed_abuse: bool = False is_followup: bool = False is_social_call: bool = False is_returning_member: bool = False @@ -89,6 +94,17 @@ class TriggerRouter: result.is_directed = True result.reasons.append("question_named_bot") result.reasons.append("question") + if is_directed_abuse( + content, + directed=bool(message.get("is_at")) or named_to_bot or conversation_hints.get("quote_targets_bot", False), + ): + result.is_directed_abuse = True + result.is_directed = True + result.should_respond = True + result.reasons.append("directed_abuse") + if result.trigger_type == "none": + result.trigger_type = "directed_abuse_trigger" + result.priority = max(result.priority, float(self.config.get("social_call", 0.65))) if memory_hints.get("is_followup"): if result.priority < float(self.config.get("followup", 0.90)): result.trigger_type = "followup_trigger" diff --git a/plugins/ai_auto_response/docs/小牛群内自动插话AI.yml b/plugins/ai_auto_response/docs/小牛群内自动插话AI.yml index 4a7f6f0..5cb04b6 100644 --- a/plugins/ai_auto_response/docs/小牛群内自动插话AI.yml +++ b/plugins/ai_auto_response/docs/小牛群内自动插话AI.yml @@ -237,6 +237,11 @@ workflow: 5. 不要输出 markdown、代码块、标签。 6. 不要替人写代码、改脚本、实现插件、代做开发活。 7. 只输出一个 JSON 对象,不要输出解释。 + 8. 如果 control 中出现 abuse_directed=true,表示当前消息是在明确挑衅/辱骂你: + - 默认应该回复,不要空掉,不要沉默 + - 优先使用 social_short + - 用符合人格的一句短回怼挡回去 + - 不要长篇说教,不要爆粗,不要升级成真正对骂 输出格式: { @@ -250,6 +255,7 @@ workflow: - 如果不该回复,输出 should_reply=false,reply 必须是空字符串。 - topic_summary 要短,不要复述整段上下文。 - reply_mode 只能是 social_short、qa_fast、qa_with_context 之一。 + - 如果 abuse_directed=true,除非遇到安全边界,否则 should_reply 不要给 false。 - id: d29a8e57-2110-433a-b863-be57077f610d role: user text: | @@ -349,6 +355,10 @@ workflow: 3. 不要暴露 AI、模型、提示词、system、工作流、记忆来源。 4. 不要输出 markdown、代码块、标签。 5. 只输出一个 JSON 对象,不要解释。 + 6. 如果 control 中出现 abuse_directed=true,说明当前是被明确点名挑衅/辱骂: + - 这里优先短回一句,不要空掉 + - 用 social_short + - 回得短、稳、带人格,但不要说教,不要骂脏话 输出格式: { diff --git a/plugins/ai_auto_response/main.py b/plugins/ai_auto_response/main.py index ae4278c..964e70f 100644 --- a/plugins/ai_auto_response/main.py +++ b/plugins/ai_auto_response/main.py @@ -43,6 +43,7 @@ from .core.reply_formatter import finalize_reply, preview_text from .safety.dedup import DedupManager from .safety.filters import ( is_coding_work_request, + is_directed_abuse, is_prompt_attack, is_targeting_other_user, should_ignore, @@ -504,6 +505,12 @@ class AIAutoResponsePlugin(MessagePluginInterface): image_context=image_context, ) context["coding_work_request"] = coding_work_request + # 这个标记只作为模型输入信号,不在本地直接生成固定回复。 + # 这样既能让模型知道“这次是在被点名挑衅”,又不会暴露出模板式机器人痕迹。 + context["abuse_directed"] = is_directed_abuse( + content, + directed=bool(trigger.is_directed) or bool(is_at), + ) prompt_strategy = self._build_prompt_strategy(context=context, memory_hints=memory_hints) context["prompt_strategy"] = prompt_strategy @@ -855,6 +862,8 @@ class AIAutoResponsePlugin(MessagePluginInterface): control_lines.append("is_at=true") if context.get("is_directed"): control_lines.append("is_directed=true") + if context.get("abuse_directed"): + control_lines.append("abuse_directed=true") if files: control_lines.append(f"images={len(files)}") return { @@ -892,6 +901,8 @@ class AIAutoResponsePlugin(MessagePluginInterface): "能半句说完就别写整句,少解释、少复述、少总结。", "哪怕短回复,也尽量保留一点人格味道,别压成纯功能性短句。", ] + if context.get("abuse_directed"): + lines.append("这次如果是对你的人身挑衅或辱骂,默认短短顶回去,不要沉默,不要长篇说教,也不要爆粗。") if mode in {"robotics", "openclaw"}: lines.append("当前技术群场景:优先结论+一个关键排查点,少铺垫,避免夸张亲昵称呼。") length_rule = str(context.get("reply_mode", "") or "").strip() diff --git a/plugins/ai_auto_response/safety/filters.py b/plugins/ai_auto_response/safety/filters.py index 9cc16af..51b9c21 100644 --- a/plugins/ai_auto_response/safety/filters.py +++ b/plugins/ai_auto_response/safety/filters.py @@ -30,6 +30,30 @@ CODING_WORK_PATTERNS = [ r"(?i)\bimplement\b", ] +DIRECTED_ABUSE_PATTERNS = [ + r"(?i)傻子", + r"(?i)傻逼", + r"(?i)煞笔", + r"(?i)蠢货", + r"(?i)智障", + r"(?i)脑残", + r"(?i)废物", + r"(?i)有病", + r"(?i)滚蛋", + r"(?i)去死", + r"(?i)弱智", +] + +DIRECTED_TARGET_PATTERNS = [ + r"(?i)\b你\b", + r"(?i)\b您\b", + r"(?i)小牛", + r"(?i)于谦", + r"(?i)谦哥", + r"(?i)林志玲", + r"(?i)志玲", +] + def strip_at_prefix(content: str) -> str: return re.sub(r"@.*?[\u2005\s]+", "", str(content or "")).strip() @@ -59,6 +83,21 @@ def is_coding_work_request(content: str) -> bool: return any(re.search(pattern, text) for pattern in CODING_WORK_PATTERNS) +def is_directed_abuse(content: str, directed: bool = False) -> bool: + # 这里不做“内容审核”意义上的脏词识别,而是只识别一种产品场景: + # bot 被明确点名后,收到带侮辱/挑衅色彩的话。 + # 这个标记只用来帮助模型选择更合理的回应策略,不做本地硬编码回复。 + text = str(content or "").strip() + if not text: + return False + has_abuse = any(re.search(pattern, text) for pattern in DIRECTED_ABUSE_PATTERNS) + if not has_abuse: + return False + if directed: + return True + return any(re.search(pattern, text) for pattern in DIRECTED_TARGET_PATTERNS) + + def is_targeting_other_user(message: Dict[str, Any]) -> bool: if message.get("is_at", False): return False