improve xiaoniu recipient cues and name-prefixed replies
This commit is contained in:
@@ -166,11 +166,18 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
"sender": sender,
|
"sender": sender,
|
||||||
"sender_name": sender_name,
|
"sender_name": sender_name,
|
||||||
"content": content,
|
"content": content,
|
||||||
|
"is_at": bool(message.get("is_at", False)),
|
||||||
"timestamp": message.get("timestamp"),
|
"timestamp": message.get("timestamp"),
|
||||||
}
|
}
|
||||||
self._append_group_message(room_id, normalized_message)
|
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)
|
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)
|
conversation_hints = self._build_conversation_hints(
|
||||||
|
recent_messages,
|
||||||
|
sender,
|
||||||
|
content,
|
||||||
|
quote_context,
|
||||||
|
self.persona_engine.config.get("name", "小牛"),
|
||||||
|
)
|
||||||
|
|
||||||
memory_hints = self.memory_store.build_memory_hints(room_id, sender)
|
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", {}))
|
self._sync_member_memory(room_id, sender, sender_name, memory_hints.get("member_context", {}))
|
||||||
@@ -183,7 +190,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
is_followup=memory_hints.get("is_followup", False),
|
is_followup=memory_hints.get("is_followup", False),
|
||||||
last_active_at=memory_hints.get("last_active_at", "") or "",
|
last_active_at=memory_hints.get("last_active_at", "") or "",
|
||||||
)
|
)
|
||||||
trigger = self.trigger_router.route(message | {"content": content}, memory_hints)
|
trigger = self.trigger_router.route(message | {"content": content}, memory_hints, conversation_hints)
|
||||||
flow_state = self.flow_manager.apply_message_event(room_id, {
|
flow_state = self.flow_manager.apply_message_event(room_id, {
|
||||||
"is_at": message.get("is_at", False),
|
"is_at": message.get("is_at", False),
|
||||||
"is_question": trigger.is_question,
|
"is_question": trigger.is_question,
|
||||||
@@ -200,6 +207,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
trigger_type=trigger.trigger_type,
|
trigger_type=trigger.trigger_type,
|
||||||
priority=trigger.priority,
|
priority=trigger.priority,
|
||||||
reasons="|".join(trigger.reasons),
|
reasons="|".join(trigger.reasons),
|
||||||
|
directed=self._yn(trigger.is_directed),
|
||||||
flow_state=flow_state.state,
|
flow_state=flow_state.state,
|
||||||
flow_score=round(flow_state.score, 2),
|
flow_score=round(flow_state.score, 2),
|
||||||
topic=trigger.topic or "",
|
topic=trigger.topic or "",
|
||||||
@@ -299,6 +307,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
return False, "empty_response"
|
return False, "empty_response"
|
||||||
|
|
||||||
reply_chunks = self._finalize_reply(response, reply_mode)
|
reply_chunks = self._finalize_reply(response, reply_mode)
|
||||||
|
reply_chunks = self._maybe_add_name_prefix(reply_chunks, sender_name, reply_mode, trigger.__dict__)
|
||||||
|
|
||||||
for chunk in reply_chunks:
|
for chunk in reply_chunks:
|
||||||
await bot.send_text_message(room_id, chunk, sender)
|
await bot.send_text_message(room_id, chunk, sender)
|
||||||
@@ -435,7 +444,13 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_conversation_hints(recent_messages: List[Dict], current_sender: str, current_content: str) -> Dict[str, Any]:
|
def _build_conversation_hints(
|
||||||
|
recent_messages: List[Dict],
|
||||||
|
current_sender: str,
|
||||||
|
current_content: str,
|
||||||
|
quote_context: Dict[str, Any],
|
||||||
|
bot_name: str,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
previous_messages = list(recent_messages[:-1]) if recent_messages else []
|
previous_messages = list(recent_messages[:-1]) if recent_messages else []
|
||||||
recent_window = previous_messages[-4:]
|
recent_window = previous_messages[-4:]
|
||||||
solver_count = 0
|
solver_count = 0
|
||||||
@@ -449,9 +464,28 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
if AIAutoResponsePlugin._looks_like_answer(content) and AIAutoResponsePlugin._has_topic_overlap(current_tokens, content):
|
if AIAutoResponsePlugin._looks_like_answer(content) and AIAutoResponsePlugin._has_topic_overlap(current_tokens, content):
|
||||||
solver_count += 1
|
solver_count += 1
|
||||||
solver_senders.add(sender)
|
solver_senders.add(sender)
|
||||||
|
previous_same_sender_directed = False
|
||||||
|
same_sender_recent_count = 0
|
||||||
|
bot_name_lower = str(bot_name or "").lower()
|
||||||
|
for item in reversed(previous_messages[-6:]):
|
||||||
|
sender = str(item.get("sender", "") or "")
|
||||||
|
if sender != current_sender:
|
||||||
|
continue
|
||||||
|
same_sender_recent_count += 1
|
||||||
|
content = str(item.get("content") or item.get("message") or "").strip().lower()
|
||||||
|
if bool(item.get("is_at")) or (bot_name_lower and bot_name_lower in content):
|
||||||
|
previous_same_sender_directed = True
|
||||||
|
break
|
||||||
|
quote_targets_bot = False
|
||||||
|
quote_sender_name = str(quote_context.get("quote_sender_name", "") or "").strip().lower()
|
||||||
|
if quote_sender_name and bot_name_lower and bot_name_lower in quote_sender_name:
|
||||||
|
quote_targets_bot = True
|
||||||
return {
|
return {
|
||||||
"has_recent_human_solver": solver_count >= 2 and len(solver_senders) >= 1,
|
"has_recent_human_solver": solver_count >= 2 and len(solver_senders) >= 1,
|
||||||
"solver_count": solver_count,
|
"solver_count": solver_count,
|
||||||
|
"previous_same_sender_directed": previous_same_sender_directed,
|
||||||
|
"same_sender_recent_count": same_sender_recent_count,
|
||||||
|
"quote_targets_bot": quote_targets_bot,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -542,6 +576,41 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
break
|
break
|
||||||
return chunks[:chunk_limit] or [text[:char_limit].strip()]
|
return chunks[:chunk_limit] or [text[:char_limit].strip()]
|
||||||
|
|
||||||
|
def _maybe_add_name_prefix(self, reply_chunks: List[str], sender_name: str, reply_mode: str, trigger: Dict[str, Any]) -> List[str]:
|
||||||
|
if not reply_chunks:
|
||||||
|
return reply_chunks
|
||||||
|
if reply_mode not in {"social_short", "qa_fast", "qa_with_context"}:
|
||||||
|
return reply_chunks
|
||||||
|
if not (trigger.get("is_at") or trigger.get("is_directed") or trigger.get("is_social_call")):
|
||||||
|
return reply_chunks
|
||||||
|
clean_name = self._clean_display_name(sender_name)
|
||||||
|
if not clean_name or len(clean_name) > 8:
|
||||||
|
return reply_chunks
|
||||||
|
first = reply_chunks[0].strip()
|
||||||
|
if not first or clean_name in first:
|
||||||
|
return reply_chunks
|
||||||
|
# 低频昵称点名:按内容稳定分桶,避免每次都叫,也避免完全随机飘忽。
|
||||||
|
bucket_seed = f"{clean_name}|{reply_mode}|{first}"
|
||||||
|
if (sum(ord(ch) for ch in bucket_seed) % 5) != 0:
|
||||||
|
return reply_chunks
|
||||||
|
prefix = f"{clean_name},"
|
||||||
|
if len(first) + len(prefix) > (16 if reply_mode == "social_short" else 42):
|
||||||
|
return reply_chunks
|
||||||
|
updated = list(reply_chunks)
|
||||||
|
updated[0] = prefix + first
|
||||||
|
return updated
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clean_display_name(sender_name: str) -> str:
|
||||||
|
text = str(sender_name or "").strip()
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
text = re.sub(r"\s+", "", text)
|
||||||
|
text = re.sub(r"[^\u4e00-\u9fffA-Za-z0-9_]", "", text)
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
return text[:8]
|
||||||
|
|
||||||
|
|
||||||
def _sync_member_memory(self, room_id: str, sender: str, sender_name: str, member_context: Dict) -> None:
|
def _sync_member_memory(self, room_id: str, sender: str, sender_name: str, member_context: Dict) -> None:
|
||||||
if not member_context:
|
if not member_context:
|
||||||
@@ -644,6 +713,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
return (
|
return (
|
||||||
f"[XIAONIU] DECIDE room={room} user={sender} "
|
f"[XIAONIU] DECIDE room={room} user={sender} "
|
||||||
f"trigger={data.get('trigger_type', 'none')} "
|
f"trigger={data.get('trigger_type', 'none')} "
|
||||||
|
f"dir={data.get('directed', '-') or '-'} "
|
||||||
f"flow={data.get('flow_state', '')}:{data.get('flow_score', '')} "
|
f"flow={data.get('flow_state', '')}:{data.get('flow_score', '')} "
|
||||||
f"topic={data.get('topic', '-') or '-'} "
|
f"topic={data.get('topic', '-') or '-'} "
|
||||||
f"reasons={data.get('reasons', '-') or '-'}"
|
f"reasons={data.get('reasons', '-') or '-'}"
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ class TriggerRouter:
|
|||||||
self.config = config or {}
|
self.config = config or {}
|
||||||
self.topic_keywords = [str(item).lower() for item in self.config.get("focus", [])]
|
self.topic_keywords = [str(item).lower() for item in self.config.get("focus", [])]
|
||||||
|
|
||||||
def route(self, message: Dict, memory_hints: Dict) -> TriggerResult:
|
def route(self, message: Dict, memory_hints: Dict, conversation_hints: Dict | None = None) -> TriggerResult:
|
||||||
|
conversation_hints = conversation_hints or {}
|
||||||
content = str(message.get("content", "")).strip()
|
content = str(message.get("content", "")).strip()
|
||||||
content_lower = content.lower()
|
content_lower = content.lower()
|
||||||
result = TriggerResult()
|
result = TriggerResult()
|
||||||
@@ -80,6 +81,17 @@ class TriggerRouter:
|
|||||||
result.priority = max(result.priority, float(self.config.get("light_social", 0.45)))
|
result.priority = max(result.priority, float(self.config.get("light_social", 0.45)))
|
||||||
result.is_social_call = True
|
result.is_social_call = True
|
||||||
result.reasons.append("light_social")
|
result.reasons.append("light_social")
|
||||||
|
if conversation_hints.get("quote_targets_bot"):
|
||||||
|
result.is_directed = True
|
||||||
|
result.should_respond = True
|
||||||
|
result.reasons.append("quote_targets_bot")
|
||||||
|
if result.trigger_type == "none":
|
||||||
|
result.trigger_type = "quote_followup_trigger"
|
||||||
|
result.priority = max(result.priority, float(self.config.get("followup", 0.90)))
|
||||||
|
if conversation_hints.get("previous_same_sender_directed") and result.is_question:
|
||||||
|
result.is_directed = True
|
||||||
|
result.should_respond = True
|
||||||
|
result.reasons.append("same_sender_directed")
|
||||||
if result.is_question and result.is_directed:
|
if result.is_question and result.is_directed:
|
||||||
result.should_respond = True
|
result.should_respond = True
|
||||||
if memory_hints.get("returning_member_state") in {"returning_member", "long_absent_member"}:
|
if memory_hints.get("returning_member_state") in {"returning_member", "long_absent_member"}:
|
||||||
|
|||||||
Reference in New Issue
Block a user