improve xiaoniu group awareness and solver suppression

This commit is contained in:
liuwei
2026-04-07 12:27:53 +08:00
parent faa5d68eb0
commit d507cdf88d
4 changed files with 112 additions and 5 deletions
+65 -3
View File
@@ -163,6 +163,8 @@ class AIAutoResponsePlugin(MessagePluginInterface):
"timestamp": message.get("timestamp"),
}
self._append_group_message(room_id, normalized_message)
recent_messages = self.group_messages.get(room_id) or self.memory_store.get_recent_messages(room_id)
conversation_hints = self._build_conversation_hints(recent_messages, sender, content)
memory_hints = self.memory_store.build_memory_hints(room_id, sender)
self._sync_member_memory(room_id, sender, sender_name, memory_hints.get("member_context", {}))
@@ -198,8 +200,15 @@ class AIAutoResponsePlugin(MessagePluginInterface):
)
allow_proactive = bool(self.mode_config.get("allow_proactive_reply", True))
acceptance_state = self.flow_manager.get_acceptance_state(room_id)
reply_mode = self.response_planner.choose_reply_mode(trigger.__dict__, flow_state.state)
should_reply = self.response_planner.should_reply(trigger.__dict__, flow_state.state, allow_proactive)
should_reply = self.response_planner.should_reply(
trigger.__dict__,
flow_state.state,
allow_proactive,
acceptance_state,
conversation_hints,
)
if not should_reply:
self._log_event(
"skip",
@@ -209,6 +218,8 @@ class AIAutoResponsePlugin(MessagePluginInterface):
trigger_type=trigger.trigger_type,
reply_mode=reply_mode,
flow_state=flow_state.state,
acceptance_state=acceptance_state,
solver=self._yn(conversation_hints.get("has_recent_human_solver")),
)
return False, "skip"
if not self._pass_cooldown(room_id, trigger.__dict__):
@@ -222,7 +233,6 @@ class AIAutoResponsePlugin(MessagePluginInterface):
)
return False, "cooldown"
recent_messages = self.group_messages.get(room_id) or self.memory_store.get_recent_messages(room_id)
vector_memories = []
if self.vector_memory.should_search(reply_mode, trigger.trigger_type, memory_hints.get("returning_member_state", "")):
vector_memories = self.vector_memory.search(content, room_id, sender)
@@ -232,6 +242,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
sender=sender,
group_mode=group_profile.get("mode", ""),
knowledge_domain=group_profile.get("knowledge_domain", ""),
acceptance_state=acceptance_state,
reply_mode=reply_mode,
recent_message_count=len(recent_messages),
vector_hit_count=len(vector_memories),
@@ -372,6 +383,54 @@ class AIAutoResponsePlugin(MessagePluginInterface):
f"12. 回答时优先服从当前群画像里的知识域和回答风格,不要跨领域乱发挥。\n"
)
@staticmethod
def _build_conversation_hints(recent_messages: List[Dict], current_sender: str, current_content: str) -> Dict[str, Any]:
previous_messages = list(recent_messages[:-1]) if recent_messages else []
recent_window = previous_messages[-4:]
solver_count = 0
solver_senders = set()
current_tokens = AIAutoResponsePlugin._extract_overlap_tokens(current_content)
for item in recent_window:
sender = str(item.get("sender", "") or "")
if not sender or sender == current_sender:
continue
content = str(item.get("content") or item.get("message") or "").strip().lower()
if AIAutoResponsePlugin._looks_like_answer(content) and AIAutoResponsePlugin._has_topic_overlap(current_tokens, content):
solver_count += 1
solver_senders.add(sender)
return {
"has_recent_human_solver": solver_count >= 2 and len(solver_senders) >= 1,
"solver_count": solver_count,
}
@staticmethod
def _looks_like_answer(content: str) -> bool:
if not content:
return False
answer_keywords = [
"", "然后", "重启", "配置", "日志", "接口", "看一下", "试试", "排查",
"报错", "原因", "因为", "改成", "", "部署", "重现", "检查", "确认",
]
if len(content) >= 18:
return True
return any(keyword in content for keyword in answer_keywords)
@staticmethod
def _extract_overlap_tokens(content: str) -> set[str]:
text = str(content or "").lower()
tokens = set(re.findall(r"[a-z0-9_\\-]{3,}", text))
for keyword in ["报错", "日志", "配置", "接口", "插件", "部署", "docker", "python", "openclaw", "机器人", "qdrant", "ollama"]:
if keyword in text:
tokens.add(keyword)
return tokens
@staticmethod
def _has_topic_overlap(current_tokens: set[str], previous_content: str) -> bool:
if not current_tokens:
return False
previous_tokens = AIAutoResponsePlugin._extract_overlap_tokens(previous_content)
return bool(current_tokens & previous_tokens)
@staticmethod
def _sanitize_response(response: str) -> str:
if not response:
@@ -527,13 +586,16 @@ class AIAutoResponsePlugin(MessagePluginInterface):
f"[XIAONIU] SKIP room={room} user={sender} "
f"reason={data.get('reason', '')} "
f"trigger={data.get('trigger_type', 'none')} "
f"mode={data.get('reply_mode', '')}"
f"mode={data.get('reply_mode', '')} "
f"acc={data.get('acceptance_state', '-') or '-'} "
f"solver={data.get('solver', '-') or '-'}"
).strip()
if event == "context":
return (
f"[XIAONIU] CTX room={room} user={sender} "
f"mode={data.get('reply_mode', '')} "
f"acc={data.get('acceptance_state', '-') or '-'} "
f"recent={data.get('recent_message_count', 0)} "
f"vector={data.get('vector_hit_count', 0)}"
).strip()