From 4cae39d153790c7996a4fe43e45b23a828341f5d Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Mon, 16 Mar 2026 23:46:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=BA=E6=A0=BC=E8=AE=BE=E5=AE=9A=E7=9A=84?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=80=89=E9=A1=B9=EF=BC=8C=E5=B9=B6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=9B=B8=E5=85=B3=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(persona): 自动检测当前群聊激活的对话所绑定的性格 --- _conf_schema.json | 6 + .../analysis/analyzers/base_analyzer.py | 124 ++++++++++++++++-- .../analysis/utils/llm_utils.py | 8 ++ src/infrastructure/config/config_manager.py | 4 + .../platform/adapters/onebot_adapter.py | 12 +- 5 files changed, 141 insertions(+), 13 deletions(-) diff --git a/_conf_schema.json b/_conf_schema.json index 75527d5..afe7f57 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -220,6 +220,12 @@ "description": "最大金句数量", "default": 5, "hint": "分析报告中显示的最大金句数量,依赖于 LLM 输出的格式化信息质量,模型越好结果越好,可能出现数量不匹配。" + }, + "keep_original_persona": { + "description": "是否保持原始人格设定参与回复", + "type": "bool", + "default": false, + "hint": "开启后,若当前会话存在人格设定,将尝试读取其 Prompt 融入系统提示词。尽量保持分析报告的说话倾向与会话人设一致。" } } }, diff --git a/src/infrastructure/analysis/analyzers/base_analyzer.py b/src/infrastructure/analysis/analyzers/base_analyzer.py index 3828778..ad1bb29 100644 --- a/src/infrastructure/analysis/analyzers/base_analyzer.py +++ b/src/infrastructure/analysis/analyzers/base_analyzer.py @@ -19,7 +19,7 @@ from ..utils.llm_utils import ( class BaseAnalyzer(ABC): """ 基础分析器抽象类 - 定义所有分析器的通用接口和流程 + 定义所有分析器的通用接口 and 流程 """ def __init__(self, context, config_manager): @@ -172,10 +172,6 @@ class BaseAnalyzer(ABC): # 保存调试数据 debug_mode = self.config_manager.get_debug_mode() - logger.info( - f"[Debug] debug_mode={debug_mode}, session_id={session_id}, prompt_len={len(prompt) if prompt else 0}" - ) # Added log - if debug_mode and session_id and prompt: self._save_debug_data(prompt, session_id) elif debug_mode and not session_id: @@ -193,14 +189,37 @@ class BaseAnalyzer(ABC): temperature = self.get_temperature() provider_id_key = self.get_provider_id_key() + # 获取人格设定 + system_prompt = await self._build_system_prompt(umo) + + # 如果开启了人格设定且成功获取到 Prompt,我们将其注入到主提示词中,以确保最佳效果 + if system_prompt: + logger.info(f"[{self.get_data_type()}分析] 已启用人格设定") + # 在主提示词前添加人格说明,并要求 LLM 保持风格 + prompt = ( + f"你可以扮演以下人格:\n{system_prompt}\n\n" + f"请在接下来的分析工作中,保持上述人格的角色定位和说话风格。\n" + "--- 任务开始 ---\n" + f"{prompt}" + ) + + logger.info(f"[{self.get_data_type()}分析] 开始发起 LLM 请求, umo: {umo}") + + # [Debug] 记录调试信息 + if debug_mode: + logger.debug( + f"[Debug] debug_mode={debug_mode}, umo={umo}, session_id={session_id}, prompt_len={len(prompt) if prompt else 0}" + ) + response = await call_provider_with_retry( self.context, self.config_manager, - prompt, - max_tokens, - temperature, - umo, - provider_id_key, + prompt=prompt, + max_tokens=max_tokens, + temperature=temperature, + umo=umo, + provider_id_key=provider_id_key, + system_prompt=system_prompt, ) if response is None: @@ -274,3 +293,88 @@ class BaseAnalyzer(ABC): 温度参数 """ return 0.6 + + 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 + + # 获取人格管理器 + 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) + persona_prompt = ( + persona_obj.system_prompt + if hasattr(persona_obj, "system_prompt") + else None + ) + if persona_prompt: + logger.debug(f"找到会话选定的人格: {persona_id}") + + # 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 + ) + 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})" + ) + + # 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 + + if not isinstance(persona_prompt, str) or not persona_prompt.strip(): + return None + + # 构建系统提示词,要求 LLM 保持人格设定 + system_prompt = persona_prompt.strip() + return system_prompt diff --git a/src/infrastructure/analysis/utils/llm_utils.py b/src/infrastructure/analysis/utils/llm_utils.py index 92c7d59..c27a76d 100644 --- a/src/infrastructure/analysis/utils/llm_utils.py +++ b/src/infrastructure/analysis/utils/llm_utils.py @@ -188,6 +188,7 @@ async def call_provider_with_retry( temperature: float, umo: str | None = None, provider_id_key: str | None = None, + system_prompt: str | None = None, ) -> Any | None: """ 调用LLM提供者,带超时、重试与退避。支持自定义服务商和配置化 Provider 选择。 @@ -200,6 +201,7 @@ async def call_provider_with_retry( temperature: 采样温度 umo: 指定使用的模型唯一标识符 provider_id_key: 配置中的 provider_id 键名(如 'topic_provider_id'),用于选择特定的 Provider + system_prompt: 系统提示词 Returns: LLM生成的结果,失败时返回None @@ -231,6 +233,11 @@ async def call_provider_with_retry( f"[LLM 调用] Prompt 前100字符: {prompt[:100] if prompt else 'None'}..." ) + if system_prompt: + logger.debug( + f"[LLM 调用] System Prompt 前100字符: {system_prompt[:100]}..." + ) + # 检查 prompt 是否为空 if not prompt or not prompt.strip(): logger.error( @@ -253,6 +260,7 @@ async def call_provider_with_retry( prompt=prompt, max_tokens=max_tokens, temperature=temperature, + system_prompt=system_prompt, ) # 成功记录 diff --git a/src/infrastructure/config/config_manager.py b/src/infrastructure/config/config_manager.py index bba2525..bcd4539 100644 --- a/src/infrastructure/config/config_manager.py +++ b/src/infrastructure/config/config_manager.py @@ -220,6 +220,10 @@ class ConfigManager: """获取金句分析专用 Provider ID""" 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_pdf_output_dir(self) -> str: """获取PDF输出目录""" try: diff --git a/src/infrastructure/platform/adapters/onebot_adapter.py b/src/infrastructure/platform/adapters/onebot_adapter.py index 78f85b4..0f61a7d 100644 --- a/src/infrastructure/platform/adapters/onebot_adapter.py +++ b/src/infrastructure/platform/adapters/onebot_adapter.py @@ -1237,7 +1237,9 @@ class OneBotAdapter(PlatformAdapter): f"[群分析相册] Base64 接口 2 (upload_group_album) 失败: {e3}" ) await self.bot.call_action("upload_qun_album", **params) - logger.info("[群分析相册] Base64 模式 (upload_qun_album) 上传成功") + logger.info( + "[群分析相册] Base64 模式 (upload_qun_album) 上传成功" + ) return True except Exception as e: @@ -1294,7 +1296,9 @@ class OneBotAdapter(PlatformAdapter): for action in actions: try: - logger.debug(f"[群分析相册] 正在通过 {action} 获取列表 (群: {group_id})...") + logger.debug( + f"[群分析相册] 正在通过 {action} 获取列表 (群: {group_id})..." + ) result = await self.bot.call_action( action, group_id=int(group_id), # 确保传整型,对齐 NapCat 等实现 @@ -1329,7 +1333,9 @@ class OneBotAdapter(PlatformAdapter): if not album_name: return None - logger.debug(f"[群分析相册] 正在群 {group_id} 中查找名为 '{album_name}' 的相册...") + logger.debug( + f"[群分析相册] 正在群 {group_id} 中查找名为 '{album_name}' 的相册..." + ) albums = await self.get_group_album_list(group_id) for album in albums: name = album.get("name") or album.get("album_name", "")