收紧 ai_auto_response 群内问句主动回复策略
- 为疑问句增加 question_detected 形态标记,区分问句形态与真正指向 bot 的提问 - 仅在 @bot、点 bot 名字或明确定向时才把疑问句升级为问答触发 - 阻断普通群问句通过 topic 主动接话路径进入模型,避免 bot 抢答群友互问 - 将 social call 收紧为 名字/别名 + 召唤词 的组合,减少 帮忙看/看看 等泛词误触发 - 在配置中增加 bot_name_keywords 与 social_call_verb_patterns,便于后续按人格扩展
This commit is contained in:
@@ -88,6 +88,11 @@ recent_followup_window_minutes = 5
|
||||
at_bot = 1.0
|
||||
explicit_question = 0.95
|
||||
question_requires_at = true
|
||||
# 这里允许“点 bot 名字”作为弱定向信号,但不会把普通群问句当成 bot 提问。
|
||||
# 只有出现这些名字/别名时,问句或社交召唤才会更像在对 bot 说话。
|
||||
bot_name_keywords = ["小牛", "xiaoniu", "于谦", "谦哥", "林志玲", "志玲"]
|
||||
# 这些词本身太泛,必须和 bot 名字一起出现,才算社交召唤。
|
||||
social_call_verb_patterns = ["在吗", "出来", "帮忙看", "看看", "说句话", "回一句", "救一下"]
|
||||
followup = 0.90
|
||||
social_call = 0.65
|
||||
light_social = 0.45
|
||||
|
||||
@@ -28,12 +28,19 @@ class ResponsePlanner:
|
||||
conversation_hints = conversation_hints or {}
|
||||
trigger_type = str(trigger.get("trigger_type", "") or "")
|
||||
directed = bool(trigger.get("is_directed"))
|
||||
question_detected = bool(trigger.get("question_detected"))
|
||||
if trigger.get("is_at") or trigger_type == "at_trigger":
|
||||
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":
|
||||
return False
|
||||
# 关键收敛:
|
||||
# 1. 群里的普通问句,哪怕命中了 topic,也不应该因为“当前气氛热”就被 bot 主动接住;
|
||||
# 2. 只要它有明显问句形态,但又没有明确指向 bot,就整体禁止进入模型,
|
||||
# 从根上阻断“别人互相问一句,bot 突然抢答”的尴尬感。
|
||||
if question_detected and not directed and not trigger.get("is_followup"):
|
||||
return False
|
||||
if trigger.get("is_question"):
|
||||
# 策略收敛:
|
||||
# 问答类回复只在“明确指向机器人”时触发,防止把群友之间的疑问句当作对机器人提问。
|
||||
|
||||
@@ -21,6 +21,11 @@ class TriggerResult:
|
||||
trigger_type: str = "none"
|
||||
priority: float = 0.0
|
||||
is_question: bool = False
|
||||
# 这里单独保留“像疑问句”的形态标记:
|
||||
# 1. 群里很多问句并不是问 bot;
|
||||
# 2. 但我们仍然希望后续决策层知道“这是一条问句形态消息”,
|
||||
# 以便阻止 topic/主动接话路径误把它当成 bot 可抢答的机会。
|
||||
question_detected: bool = False
|
||||
is_directed: bool = False
|
||||
is_followup: bool = False
|
||||
is_social_call: bool = False
|
||||
@@ -34,6 +39,20 @@ class TriggerRouter:
|
||||
def __init__(self, config: Dict):
|
||||
self.config = config or {}
|
||||
self.topic_keywords = [str(item).lower() for item in self.config.get("focus", [])]
|
||||
# 这里把“bot 名称/别名”做成可配置项,便于不同人格共享同一套触发逻辑。
|
||||
# 这样“普通群友之间的问句”和“明确点 bot 名字的问句”可以分开处理。
|
||||
self.bot_name_keywords = [
|
||||
str(item).strip().lower()
|
||||
for item in (self.config.get("bot_name_keywords", []) or [])
|
||||
if str(item).strip()
|
||||
] or ["小牛", "xiaoniu", "于谦", "谦哥", "林志玲", "志玲"]
|
||||
# 这些是明显带有“朝某个对象发话”味道的词。
|
||||
# 只有名字 + 这些动词/语气组合在一起,才更像是在叫 bot 接话。
|
||||
self.social_call_verb_patterns = [
|
||||
str(item).strip()
|
||||
for item in (self.config.get("social_call_verb_patterns", []) or [])
|
||||
if str(item).strip()
|
||||
] or [r"在吗", r"出来", r"帮忙看", r"看看", r"说句话", r"回一句", r"救一下"]
|
||||
# 业务约束说明:
|
||||
# 1) 群内大量“疑问句”其实是群友之间的随口沟通,不是向机器人求助;
|
||||
# 2) 如果把所有疑问句都当成问答触发,会显著提升误触发率,导致“高频答非所问”;
|
||||
@@ -44,6 +63,7 @@ class TriggerRouter:
|
||||
conversation_hints = conversation_hints or {}
|
||||
content = str(message.get("content", "")).strip()
|
||||
content_lower = content.lower()
|
||||
named_to_bot = self._has_bot_name(content_lower)
|
||||
result = TriggerResult()
|
||||
if message.get("is_at"):
|
||||
result.trigger_type = "at_trigger"
|
||||
@@ -52,16 +72,22 @@ class TriggerRouter:
|
||||
result.is_directed = True
|
||||
result.reasons.append("is_at")
|
||||
if self._is_question(content):
|
||||
result.question_detected = True
|
||||
result.reasons.append("question_shape")
|
||||
# 这里把“是否是疑问句”和“是否应该按咨询触发”拆开处理:
|
||||
# - 疑问句形态用于日志观察;
|
||||
# - 触发升级只在满足定向条件时才生效,避免群聊里普通问句频繁触发机器人。
|
||||
if self.question_requires_at and not message.get("is_at"):
|
||||
result.reasons.append("question_detected_but_not_at")
|
||||
# - 触发升级只在满足“明确指向 bot”条件时才生效,避免群聊里普通问句频繁触发机器人。
|
||||
directed_question = bool(message.get("is_at")) or named_to_bot or not self.question_requires_at
|
||||
if not directed_question:
|
||||
result.reasons.append("question_detected_but_not_directed")
|
||||
else:
|
||||
if result.priority < float(self.config.get("explicit_question", 0.95)):
|
||||
result.trigger_type = "question_trigger"
|
||||
result.priority = float(self.config.get("explicit_question", 0.95))
|
||||
result.is_question = True
|
||||
if named_to_bot and not result.is_directed:
|
||||
result.is_directed = True
|
||||
result.reasons.append("question_named_bot")
|
||||
result.reasons.append("question")
|
||||
if memory_hints.get("is_followup"):
|
||||
if result.priority < float(self.config.get("followup", 0.90)):
|
||||
@@ -80,7 +106,7 @@ class TriggerRouter:
|
||||
if result.priority < float(self.config.get("casual_topic", 0.35)):
|
||||
result.trigger_type = result.trigger_type if result.trigger_type != "none" else "topic_trigger"
|
||||
result.priority = max(result.priority, float(self.config.get("casual_topic", 0.35)))
|
||||
if self._is_social_call(content_lower):
|
||||
if self._is_social_call(content):
|
||||
if result.priority < float(self.config.get("social_call", 0.65)):
|
||||
result.trigger_type = result.trigger_type if result.trigger_type != "none" else "social_trigger"
|
||||
result.priority = max(result.priority, float(self.config.get("social_call", 0.65)))
|
||||
@@ -119,7 +145,19 @@ class TriggerRouter:
|
||||
return any(re.search(pattern, content, flags=re.IGNORECASE) for pattern in QUESTION_PATTERNS)
|
||||
|
||||
def _is_social_call(self, content: str) -> bool:
|
||||
return any(re.search(pattern, content, flags=re.IGNORECASE) for pattern in SOCIAL_PATTERNS)
|
||||
text = str(content or "").strip()
|
||||
lower = text.lower()
|
||||
# 社交召唤要尽量保守:
|
||||
# 1. 必须先出现 bot 名称/别名;
|
||||
# 2. 再配合“在吗/帮忙看/看看”这类召唤词,或明显疑问语气;
|
||||
# 这样像“帮忙看一下这个报错”这种群友互问,就不会误触发 bot 接话。
|
||||
if not self._has_bot_name(lower):
|
||||
return False
|
||||
if any(re.search(pattern, text, flags=re.IGNORECASE) for pattern in self.social_call_verb_patterns):
|
||||
return True
|
||||
if self._is_question(text):
|
||||
return True
|
||||
return text.endswith(("?", "?"))
|
||||
|
||||
def _is_light_social_moment(self, content: str) -> bool:
|
||||
if len(content) > 24:
|
||||
@@ -131,3 +169,9 @@ class TriggerRouter:
|
||||
if keyword and keyword in content_lower:
|
||||
return keyword
|
||||
return ""
|
||||
|
||||
def _has_bot_name(self, content_lower: str) -> bool:
|
||||
text = str(content_lower or "").strip().lower()
|
||||
if not text:
|
||||
return False
|
||||
return any(keyword and keyword in text for keyword in self.bot_name_keywords)
|
||||
|
||||
Reference in New Issue
Block a user