add group-aware persona bias for xiaoniu bot

This commit is contained in:
liuwei
2026-04-07 12:10:47 +08:00
parent d6abb1cc23
commit 1996df7b99
8 changed files with 442 additions and 28 deletions

View File

@@ -16,6 +16,8 @@ from wechat_ipad.models.message import MessageType
from .context_builder import ContextBuilder
from .flow_manager import FlowManager
from .group_memory import GroupMemoryService
from .group_profile import GroupProfileResolver
from .llm_client import LLMClient
from .memory_store import MemoryStore
from .persona_engine import PersonaEngine
@@ -72,6 +74,8 @@ class AIAutoResponsePlugin(MessagePluginInterface):
self.db_manager = context.get("db_manager")
self.enable = bool(self._config.get("enable", True))
self.persona_engine = PersonaEngine(self.get_plugin_path(), self._config.get("persona", {}))
self.group_memory_service = GroupMemoryService(self.db_manager, self._config.get("group_profiles", {}) or {})
self.group_profile_resolver = GroupProfileResolver(self._config.get("group_profiles", {}) or {})
self.flow_manager = FlowManager({
**(self._config.get("flow", {}) or {}),
"night_silent_hours": (self._config.get("cooldown", {}) or {}).get("night_silent_hours", []),
@@ -134,11 +138,19 @@ class AIAutoResponsePlugin(MessagePluginInterface):
bot: WechatAPIClient = message.get("bot")
content = self._normalize_content(message)
sender_name = self._get_sender_name(room_id, sender)
group_name = self._get_group_name(room_id, message)
group_memory_profile = self.group_memory_service.build_group_memory_profile(room_id, group_name)
group_profile = self.group_profile_resolver.resolve(room_id, group_name, group_memory_profile)
self._log_event(
"recv",
room_id=room_id,
sender=sender,
sender_name=sender_name,
group_mode=group_profile.get("mode", ""),
knowledge_domain=group_profile.get("knowledge_domain", ""),
memory_domain=group_profile.get("group_memory_domain", ""),
humor_style=group_profile.get("humor_style", ""),
sharpness_style=group_profile.get("sharpness_style", ""),
is_at=message.get("is_at", False),
content_preview=self._preview(content),
msg_type=str(message.get("type")),
@@ -218,6 +230,8 @@ class AIAutoResponsePlugin(MessagePluginInterface):
"context",
room_id=room_id,
sender=sender,
group_mode=group_profile.get("mode", ""),
knowledge_domain=group_profile.get("knowledge_domain", ""),
reply_mode=reply_mode,
recent_message_count=len(recent_messages),
vector_hit_count=len(vector_memories),
@@ -225,6 +239,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
context = self.context_builder.build(
room_id=room_id,
group_profile=group_profile,
sender=sender,
sender_name=sender_name,
content=content,
@@ -236,7 +251,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
vector_memories=vector_memories,
)
system_prompt = self.persona_engine.build_system_prompt()
system_prompt = self.persona_engine.build_system_prompt(group_profile)
user_prompt = self._build_user_prompt(context, memory_hints)
response = self._sanitize_response(self.llm_client.chat(system_prompt, user_prompt, user_id=f"{room_id}:{sender}"))
if not response:
@@ -314,6 +329,11 @@ class AIAutoResponsePlugin(MessagePluginInterface):
except Exception:
return sender
@staticmethod
def _get_group_name(room_id: str, message: Dict[str, Any]) -> str:
all_contacts = message.get("all_contacts", {}) or {}
return str(all_contacts.get(room_id, room_id))
def _pass_cooldown(self, room_id: str, trigger: Dict) -> bool:
current_ts = time.time()
room_cd = int(self.cooldown_config.get("group_reply_cooldown_sec", 45))
@@ -333,6 +353,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
f"触发类型:{context.get('trigger_type', 'none')}\n"
f"回复模式:{context.get('reply_mode', 'social_short')}\n"
f"当前心流状态:{context.get('flow_state', 'idle')}\n"
f"当前群画像:\n{context.get('group_profile_prompt', '暂无')}\n\n"
f"成员稳定记忆:\n{context.get('memory_prompt', '暂无')}\n\n"
f"向量召回记忆:\n{context.get('vector_memory_prompt', '') or '暂无'}\n\n"
f"补充信息:回归状态={memory_hints.get('returning_member_state', '') or 'none'}\n"
@@ -348,6 +369,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
f"9. 如果你不确定自己是否理解对了,就宁可不展开,只回很短。\n"
f"10. 把这次回复当作真人聊天里的第一反应,先只给第一层结论,不要主动补第二层解释。\n"
f"11. 如果一句话已经够了,就立刻停,不要为了完整而补充。\n"
f"12. 回答时优先服从当前群画像里的知识域和回答风格,不要跨领域乱发挥。\n"
)
@staticmethod
@@ -478,7 +500,9 @@ class AIAutoResponsePlugin(MessagePluginInterface):
if event == "recv":
return (
f"[XIAONIU] RECV room={room} user={sender_name}/{sender} "
f"at={self._yn(data.get('is_at'))} msg={data.get('content_preview', '')}"
f"at={self._yn(data.get('is_at'))} "
f"style={self._style_mark(data.get('humor_style', ''), data.get('sharpness_style', ''))} "
f"msg={data.get('content_preview', '')}"
).strip()
if event == "memory":
@@ -552,3 +576,9 @@ class AIAutoResponsePlugin(MessagePluginInterface):
if len(value) <= 10:
return value
return value[:4] + "..." + value[-4:]
@staticmethod
def _style_mark(humor_style: str, sharpness_style: str) -> str:
humor = "humor" if "中等" in str(humor_style) or "偏上" in str(humor_style) else "plain"
sharp = "sharp" if "毒舌" in str(sharpness_style) or "嘴欠" in str(sharpness_style) else "soft"
return f"{humor}/{sharp}"