diff --git a/README.md b/README.md index 788390b..b3398f8 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,19 @@ _✨ 一个基于 AstrBot 的智能群聊分析插件,支持 **QQ (OneBot)** - 机器人根据文件名和 `html_base_url` 拼接成完整链接发送到群里。 - **注意**:本插件**不提供** Web 服务器功能,你需要自行使用 Nginx 或 AstrBot 所在的服务器环境来实现静态文件的公网访问。 + +## 人格设定 (Persona) + +插件支持深度的“人格化”分析,让 AI 能够以特定的人设风格(口吻、偏好、语气)来产出摘要和锐评。 + +### 人格识别优先级 +插件在构建分析任务时,会按以下顺序确定最终使用的“人设状态”: + +1. **强制插件人格 (优先级最高)**:在配置中开启 `强制使用插件指定人格` 并选择 ID。此时 **全平台、所有群聊** 都会统一使用这一种人设,忽略群聊本身的设置。 +2. **继承会话人设 (优先级次之)**:在配置中开启 `继承会话人设风格`。插件会尝试识别当前群聊在 AstrBot 中设置的人格(如通过 `/persona` 指令设置的人设)。如果该群已有人设,分析报告将尽量模拟其说话倾向。 +3. **系统默认人设**:若上述开关均关闭,或未识别到有效人设,则回退到当前默认设定。 + + ## 常见问题 (FAQ) ### 获取不到带记录的引用消息 diff --git a/_conf_schema.json b/_conf_schema.json index fe02b4a..967469a 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -238,10 +238,23 @@ "hint": "分析报告中显示的最大金句数量,依赖于 LLM 输出的格式化信息质量,模型越好结果越好,可能出现数量不匹配。" }, "keep_original_persona": { - "description": "是否保持原始人格设定参与回复", + "description": "继承会话人设风格", "type": "bool", "default": false, - "hint": "开启后,若当前会话存在人格设定,将尝试读取其 Prompt 融入系统提示词。尽量保持分析报告的说话倾向与会话人设一致。" + "hint": "【优先级2】开启后将自动尝试识别当前群聊会话(或对话)的人格设定。若该群已设置人设,则分析报告会尽量模拟该人设的口吻进行回复。注意:此开关在“强制使用插件指定人格”关闭时才会生效。" + }, + "use_plugin_specific_persona": { + "description": "强制使用插件指定人格", + "type": "bool", + "default": false, + "hint": "【优先级1:核心开关】若开启,将完全忽略当前群聊的人格设定,统一强制使用下方选择的“插件指定人格 ID”进行分析。适用于希望全群分析保持某种统一固定风格(如特定的“记录员”人设)的情况。" + }, + "plugin_specific_persona_id": { + "description": "插件指定人格 ID", + "type": "string", + "_special": "select_persona", + "default": "", + "hint": "当“强制使用插件指定人格”开启时,在此处选择系统中已定义的人设。若此处未选择,则仍会尝试识别全局默认人格。" } } }, diff --git a/src/infrastructure/analysis/analyzers/base_analyzer.py b/src/infrastructure/analysis/analyzers/base_analyzer.py index 8fa9728..b4fb762 100644 --- a/src/infrastructure/analysis/analyzers/base_analyzer.py +++ b/src/infrastructure/analysis/analyzers/base_analyzer.py @@ -474,85 +474,115 @@ class BaseAnalyzer(ABC, Generic[TDataObject]): async def _build_system_prompt(self, umo: str | None) -> str | None: """ - 构建带有会话人格的系统提示词 - """ - keep = self.config_manager.get_keep_original_persona() - if not keep or not umo: - return None + 构建带有会话人格的系统提示词,优先级如下: + 1. 插件指定的全局人格 (若核心开关开启) + 2. 会话/对话选定的人格 (若开启了继承开关) + 3. 当前 UMO 的默认人格 (若开启了继承开关) - # 获取人格管理器 + Args: + umo: 用户模型对象标识,用于定位会话上下文 + + Returns: + 最终生成的 System Prompt 字符串,若无则返回 None + """ + # 获取配置 + use_specific = self.config_manager.get_use_plugin_specific_persona() + specific_id = self.config_manager.get_plugin_specific_persona_id() + keep_original = self.config_manager.get_keep_original_persona() + + # 获取 AstrBot 核心的人格管理器 persona_mgr = getattr(self.context, "persona_manager", None) if persona_mgr is None: return None persona_prompt = None - try: - # 1. 尝试从 SharedPreferences 获取当前会话选中的人格 ID (类似 /persona 设置的) - from astrbot.api import sp - # resolve_selected_persona 的简化逻辑 - session_service_config = await sp.get_async( - scope="umo", scope_id=str(umo), key="session_service_config", default={} - ) - persona_id = ( - session_service_config.get("persona_id") - if session_service_config - else None - ) - - if persona_id and persona_id != "[%None]": - # 获取指定人格 - persona_obj = await persona_mgr.get_persona(persona_id) + # --- 优先级 1: 插件指定的全局固定人格 --- + # 适用于希望所有分析报告都呈现同一种风格的情况 + if use_specific and specific_id: + try: + persona_obj = await persona_mgr.get_persona(specific_id) persona_prompt = ( persona_obj.system_prompt if hasattr(persona_obj, "system_prompt") else None ) if persona_prompt: - logger.debug(f"找到会话选定的人格: {persona_id}") + logger.debug(f"已应用插件指定的全局强制人格设定: {specific_id}") + except Exception as e: + logger.warning(f"获取插件指定人格失败 (ID: {specific_id}): {e}") - # 2. 如果没有选定人格,尝试获取当前对话的人格 ID (Dialogue Persona) - if not persona_prompt: - conv_mgr = getattr(self.context, "conversation_manager", None) - if conv_mgr: - curr_conv_id = await conv_mgr.get_curr_conversation_id(umo) - if curr_conv_id: - conv_obj = await conv_mgr.get_conversation(umo, curr_conv_id) - if ( - conv_obj - and conv_obj.persona_id - and conv_obj.persona_id != "[%None]" - ): - persona_obj = await persona_mgr.get_persona( - conv_obj.persona_id + # --- 优先级 2: 继承当前会话/群聊的原始人格 --- + # 只有在未开启“强制人格”且开启了“继承设定”时生效 + if not persona_prompt and keep_original and umo: + try: + # 2.1 尝试获取 SharedPreferences 中会话绑定的 Persona ID (通常是 /persona 命令设置的) + from astrbot.api import sp + + session_service_config = await sp.get_async( + scope="umo", + scope_id=str(umo), + key="session_service_config", + default={}, + ) + persona_id = ( + session_service_config.get("persona_id") + if session_service_config + else None + ) + + if persona_id and persona_id != "[%None]": + persona_obj = await persona_mgr.get_persona(persona_id) + persona_prompt = ( + persona_obj.system_prompt + if hasattr(persona_obj, "system_prompt") + else None + ) + if persona_prompt: + logger.debug(f"继承到会话选定人格: {persona_id}") + + # 2.2 若无会话绑定,尝试获取当前对话(Dialogue)级别的人格 + if not persona_prompt: + conv_mgr = getattr(self.context, "conversation_manager", None) + if conv_mgr: + curr_conv_id = await conv_mgr.get_curr_conversation_id(umo) + if curr_conv_id: + conv_obj = await conv_mgr.get_conversation( + umo, curr_conv_id ) - persona_prompt = ( - persona_obj.system_prompt - if hasattr(persona_obj, "system_prompt") - else None - ) - if persona_prompt: - logger.debug( - f"找到对话设定的人格: {conv_obj.persona_id} (conv_id: {curr_conv_id})" + if ( + conv_obj + and conv_obj.persona_id + and conv_obj.persona_id != "[%None]" + ): + persona_obj = await persona_mgr.get_persona( + conv_obj.persona_id ) + persona_prompt = ( + persona_obj.system_prompt + if hasattr(persona_obj, "system_prompt") + else None + ) + if persona_prompt: + logger.debug( + f"继承到对话(Dialogue)设定人格: {conv_obj.persona_id}" + ) - # 3. 如果还是没有,回退到 UMO 默认人格 - if not persona_prompt: - personality = await persona_mgr.get_default_persona_v3(umo) - if isinstance(personality, dict): - persona_prompt = personality.get("prompt") - else: - persona_prompt = getattr(personality, "prompt", None) - if persona_prompt: - logger.debug("使用 UMO 默认人格设定") + # 2.3 若仍无结果,尝试获取 UMO 设定的默认人格 + if not persona_prompt: + personality = await persona_mgr.get_default_persona_v3(umo) + if isinstance(personality, dict): + persona_prompt = personality.get("prompt") + else: + persona_prompt = getattr(personality, "prompt", None) + if persona_prompt: + logger.debug("继承到 UMO 默认人格设定") - except Exception as e: - logger.warning(f"获取人格设定失败 (umo: {umo}): {e}") - return None + except Exception as e: + logger.warning(f"分析人格回溯识别失败 (umo: {umo}): {e}") + # 检查生成结果 if not isinstance(persona_prompt, str) or not persona_prompt.strip(): return None - # 构建系统提示词,要求 LLM 保持人格设定 - system_prompt = persona_prompt.strip() - return system_prompt + return persona_prompt.strip() diff --git a/src/infrastructure/config/config_manager.py b/src/infrastructure/config/config_manager.py index 6c26fd4..beb1ac6 100644 --- a/src/infrastructure/config/config_manager.py +++ b/src/infrastructure/config/config_manager.py @@ -225,9 +225,21 @@ class ConfigManager: return self._get_group("llm").get("golden_quote_provider_id", "") def get_keep_original_persona(self) -> bool: - """获取是否保持原始人格设定""" + """获取是否继承会话原始人格设定""" return self._get_group("analysis_features").get("keep_original_persona", False) + def get_use_plugin_specific_persona(self) -> bool: + """获取是否强制使用插件指定的人格设定""" + return self._get_group("analysis_features").get( + "use_plugin_specific_persona", False + ) + + def get_plugin_specific_persona_id(self) -> str: + """获取插件指定的全局人格 ID (通过 select_persona 接口选择)""" + return self._get_group("analysis_features").get( + "plugin_specific_persona_id", "" + ) + def get_pdf_output_dir(self) -> str: """获取PDF输出目录""" from pathlib import Path