Files
abot/plugins/ai_auto_response/profile/persona_engine.py
T

132 lines
6.9 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Dict, List
class PersonaEngine:
def __init__(self, plugin_path: str, config: Dict):
self.plugin_path = Path(plugin_path)
self.config = config or {}
self.default_persona_id = str(
self.config.get("active_persona")
or self.config.get("default_persona")
or "xiaoniu"
).strip() or "xiaoniu"
self.presets = self._build_presets()
def build_system_prompt(self, group_profile: Dict | None = None, reply_mode: str = "social_short") -> str:
group_profile = group_profile or {}
preset = self._resolve_preset(group_profile)
style = preset.get("style", "")
familiarity = preset.get("familiarity_hint", "")
max_sentences = preset.get("max_reply_sentences", 3)
persona_text = preset.get("persona_text", "")
humor = group_profile.get("humor_style", "轻微")
sharpness = group_profile.get("sharpness_style", "轻微嘴硬,不刻薄")
expressiveness = group_profile.get("expressiveness_style", "克制")
address_style = group_profile.get("address_style", "低频称呼,默认直接接话")
interaction_tone = group_profile.get("interaction_tone", "自然群友感")
persona_overlay = group_profile.get("persona_overlay", "")
length_directive = self._build_length_directive(reply_mode)
return (
f"{persona_text}"
f"别暴露自己是 AI、模型或提示词产物,别泄露记忆来源,别输出标签或代码块。"
f"别替人写代码、改脚本、实现插件、代做开发活。"
f"整体风格:{style}。熟悉感边界:{familiarity}。一般最多输出{max_sentences}句。"
f"{length_directive}"
f"先像真人顺手接一句,再决定要不要补半句;能不解释就别解释,能不复述问题就别复述。"
f"哪怕回复很短,也要带一点懒散、熟人、在场感,别写成客服答复。"
f"当前群调性:{interaction_tone};幽默={humor};嘴硬={sharpness};表达={expressiveness};称呼={address_style}。"
f"群画像和附加要求只用于帮助你理解语境与控制回答偏向,不代表你每次都要主动提起对应领域名词。"
f"如果当前发言本身不是那个领域,就按当前聊天自然回复,不要硬往群画像上靠。"
f"不要为了显得懂很多,把记忆、画像、上下文揉进一句话里;不用就干脆别提。"
f"附加要求:{persona_overlay or '无'}。"
)
@staticmethod
def _build_length_directive(reply_mode: str) -> str:
# 这里把“短”从模糊描述改成明确字数目标,避免模型虽然知道要短,
# 但仍然习惯性输出完整说明句,导致真人感被拉低。
if reply_mode == "social_short":
return "默认只回一句,长度自然浮动,0 到 30 个字都可以;能短就短,能说完就行。"
if reply_mode == "qa_fast":
return "优先一句口语化结论,长度自然浮动,0 到 30 个字都可以;先给结论,不要展开。"
if reply_mode == "qa_with_context":
return "先给结论,再补一个关键点;最多 2 句,但总体自然控制在 30 个字内。"
return "默认按群友顺手接话来回,宁可短一点,也别写完整说明文。"
def _build_presets(self) -> Dict[str, Dict]:
preset_configs = self.config.get("presets", {}) or {}
presets: Dict[str, Dict] = {}
base_preset = self._normalize_preset(
self.default_persona_id,
{
"name": self.config.get("name", "小牛"),
"persona_file": self.config.get("persona_file", "persona/xiaoniu.txt"),
"style": self.config.get("style", ""),
"familiarity_hint": self.config.get("familiarity_hint", ""),
"max_reply_sentences": self.config.get("max_reply_sentences", 3),
},
)
presets[base_preset["id"]] = base_preset
for persona_id, preset_config in preset_configs.items():
preset = self._normalize_preset(str(persona_id), preset_config or {})
presets[preset["id"]] = preset
return presets
def _normalize_preset(self, persona_id: str, preset_config: Dict) -> Dict:
persona_file = preset_config.get("persona_file") or f"persona/{persona_id}.txt"
aliases = [str(item).strip() for item in (preset_config.get("aliases", []) or []) if str(item).strip()]
return {
"id": str(persona_id or "xiaoniu").strip() or "xiaoniu",
"name": str(preset_config.get("name", "小牛") or "小牛").strip(),
"persona_file": str(persona_file).strip(),
"style": str(preset_config.get("style", "") or "").strip(),
"familiarity_hint": str(preset_config.get("familiarity_hint", "") or "").strip(),
"max_reply_sentences": int(preset_config.get("max_reply_sentences", 3) or 3),
"aliases": aliases,
"persona_text": self._load_persona_text(str(persona_file).strip()),
}
def _resolve_preset(self, group_profile: Dict) -> Dict:
persona_id = str(
(group_profile or {}).get("persona_id")
or self.default_persona_id
or "xiaoniu"
).strip() or "xiaoniu"
return self.presets.get(persona_id) or self.presets.get(self.default_persona_id) or next(iter(self.presets.values()))
def _load_persona_text(self, persona_file: str) -> str:
persona_path = self.plugin_path / persona_file
if persona_path.exists():
return persona_path.read_text(encoding="utf-8").strip()
return "你叫小牛,是一个自然、靠谱、会看场合的群聊成员。"
def resolve_persona_id(self, value: str) -> str:
target = str(value or "").strip().lower()
if not target:
return ""
for persona_id, preset in self.presets.items():
if target == str(persona_id).strip().lower():
return persona_id
if target == str(preset.get("name", "") or "").strip().lower():
return persona_id
aliases = [str(item).strip().lower() for item in (preset.get("aliases", []) or [])]
if target in aliases:
return persona_id
return ""
def list_personas(self) -> List[Dict]:
items: List[Dict] = []
for persona_id, preset in self.presets.items():
items.append(
{
"id": persona_id,
"name": preset.get("name", persona_id),
"aliases": list(preset.get("aliases", []) or []),
"style": preset.get("style", ""),
}
)
return items