improve xiaoniu recipient cues and name-prefixed replies
This commit is contained in:
@@ -166,11 +166,18 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
"sender": sender,
|
||||
"sender_name": sender_name,
|
||||
"content": content,
|
||||
"is_at": bool(message.get("is_at", False)),
|
||||
"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)
|
||||
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)
|
||||
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),
|
||||
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, {
|
||||
"is_at": message.get("is_at", False),
|
||||
"is_question": trigger.is_question,
|
||||
@@ -200,6 +207,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
trigger_type=trigger.trigger_type,
|
||||
priority=trigger.priority,
|
||||
reasons="|".join(trigger.reasons),
|
||||
directed=self._yn(trigger.is_directed),
|
||||
flow_state=flow_state.state,
|
||||
flow_score=round(flow_state.score, 2),
|
||||
topic=trigger.topic or "",
|
||||
@@ -299,6 +307,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
return False, "empty_response"
|
||||
|
||||
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:
|
||||
await bot.send_text_message(room_id, chunk, sender)
|
||||
@@ -435,7 +444,13 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
)
|
||||
|
||||
@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 []
|
||||
recent_window = previous_messages[-4:]
|
||||
solver_count = 0
|
||||
@@ -449,9 +464,28 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
if AIAutoResponsePlugin._looks_like_answer(content) and AIAutoResponsePlugin._has_topic_overlap(current_tokens, content):
|
||||
solver_count += 1
|
||||
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 {
|
||||
"has_recent_human_solver": solver_count >= 2 and len(solver_senders) >= 1,
|
||||
"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
|
||||
@@ -542,6 +576,41 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
break
|
||||
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:
|
||||
if not member_context:
|
||||
@@ -644,6 +713,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
return (
|
||||
f"[XIAONIU] DECIDE room={room} user={sender} "
|
||||
f"trigger={data.get('trigger_type', 'none')} "
|
||||
f"dir={data.get('directed', '-') or '-'} "
|
||||
f"flow={data.get('flow_state', '')}:{data.get('flow_score', '')} "
|
||||
f"topic={data.get('topic', '-') or '-'} "
|
||||
f"reasons={data.get('reasons', '-') or '-'}"
|
||||
|
||||
@@ -35,7 +35,8 @@ class TriggerRouter:
|
||||
self.config = config or {}
|
||||
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_lower = content.lower()
|
||||
result = TriggerResult()
|
||||
@@ -80,6 +81,17 @@ class TriggerRouter:
|
||||
result.priority = max(result.priority, float(self.config.get("light_social", 0.45)))
|
||||
result.is_social_call = True
|
||||
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:
|
||||
result.should_respond = True
|
||||
if memory_hints.get("returning_member_state") in {"returning_member", "long_absent_member"}:
|
||||
|
||||
Reference in New Issue
Block a user