From cbad858026db4b108983a6984794a7c683f43542 Mon Sep 17 00:00:00 2001 From: minecraft1024a Date: Sat, 25 Oct 2025 09:30:20 +0800 Subject: [PATCH] =?UTF-8?q?refactor(proactive=5Fthinker):=20=E5=B0=86?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=9E=84=E5=BB=BA=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E8=87=B3=E7=8B=AC=E7=AB=8B=E7=9A=84prompt?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 `ProactiveThinkerExecutor` 中用于构建决策(decision)和计划(plan)的提示词字符串的逻辑,提取并重构到新的 `prompts.py` 模块中。 这一重构旨在: - **提升可维护性**:将复杂的提示词模板与核心业务逻辑分离,使代码结构更清晰。 - **简化代码**:`proactive_thinker_executor.py` 的代码量大幅减少,职责更加单一,专注于执行流程。 - **便于管理**:集中管理所有相关的提示词模板,方便未来进行统一的调整和优化。 --- .../proactive_thinker_executor.py | 382 ++++-------------- .../built_in/proactive_thinker/prompts.py | 97 +++++ 2 files changed, 184 insertions(+), 295 deletions(-) create mode 100644 src/plugins/built_in/proactive_thinker/prompts.py diff --git a/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py b/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py index 3f5257790..bc7bd374e 100644 --- a/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py +++ b/src/plugins/built_in/proactive_thinker/proactive_thinker_executor.py @@ -9,6 +9,7 @@ from src.chat.utils.prompt import Prompt from src.common.logger import get_logger from src.config.config import global_config, model_config from src.mood.mood_manager import mood_manager +from .prompts import DECISION_PROMPT, PLAN_PROMPT from src.person_info.person_info import get_person_info_manager from src.plugin_system.apis import ( chat_api, @@ -80,7 +81,51 @@ class ProactiveThinkerExecutor: ) logger.info(f"决策结果为:回复。话题: {topic}") - plan_prompt = self._build_plan_prompt(context, start_mode, topic, reason) + # 根据聊天类型构建特定上下文 + if context["chat_type"] == "private": + user_info = context["user_info"] + relationship = context["relationship"] + target_user_or_group = f"你的朋友 '{user_info.user_nickname}'" + context_specific_block = f""" +1. **你的日程**: +{context["schedule_context"]} +2. **你和Ta的关系**: + - 详细印象: {relationship["impression"]} + - 好感度: {relationship["attitude"]}/100 +3. **最近的聊天摘要**: +{context["recent_chat_history"]} +4. **你最近的相关动作**: +{context["action_history_context"]} +""" + else: # group + group_info = context["group_info"] + target_user_or_group = f"群聊 '{group_info['group_name']}'" + context_specific_block = f""" +1. **你的日程**: +{context["schedule_context"]} +2. **群聊信息**: + - 群名称: {group_info["group_name"]} +3. **最近的聊天摘要**: +{context["recent_chat_history"]} +4. **你最近的相关动作**: +{context["action_history_context"]} +""" + + plan_prompt = PLAN_PROMPT.format( + bot_nickname=global_config.bot.nickname, + persona_core=context["persona"]["core"], + persona_side=context["persona"]["side"], + identity=context["persona"]["identity"], + current_time=context["current_time"], + target_user_or_group=target_user_or_group, + reason=reason, + topic=topic, + context_specific_block=context_specific_block, + mood_state=context["mood_state"], + ) + + if global_config.debug.show_prompt: + logger.info(f"主动思考回复器原始提示词:{plan_prompt}") is_success, response, _, _ = await llm_api.generate_with_model( prompt=plan_prompt, model_config=model_config.model_task_config.replyer @@ -222,150 +267,54 @@ class ProactiveThinkerExecutor: logger.warning(f"Stream {stream_id} 既没有 group_info 也没有 user_info") return None - def _build_decision_prompt(self, context: dict[str, Any], start_mode: str) -> str: - """ - 根据收集到的上下文信息,构建用于决策的提示词。 - - Args: - context: 包含所有上下文信息的字典。 - start_mode: 启动模式 ('cold_start' 或 'wake_up')。 - - Returns: - 构建完成的决策提示词字符串。 - """ - chat_type = context["chat_type"] - persona = context["persona"] - - # 构建通用头部 - prompt = f""" -# 角色 -你的名字是{global_config.bot.nickname},你的人设如下: -- 核心人设: {persona["core"]} -- 侧面人设: {persona["side"]} -- 身份: {persona["identity"]} - -你的当前情绪状态是: {context["mood_state"]} - -# 你最近的相关决策历史 (供参考) -{context["action_history_context"]} -""" - # 根据聊天类型构建任务和情境 - if chat_type == "private": - user_info = context["user_info"] - relationship = context["relationship"] - prompt += f""" -# 任务 -现在是 {context["current_time"]},你需要根据当前的情境,决定是否要主动向用户 '{user_info.user_nickname}' 发起对话。 - -# 情境分析 -1. **启动模式**: {start_mode} ({"初次见面/很久未见" if start_mode == "cold_start" else "日常唤醒"}) -2. **你的日程**: -{context["schedule_context"]} -3. **你和Ta的关系**: - - 简短印象: {relationship["short_impression"]} - - 详细印象: {relationship["impression"]} - - 好感度: {relationship["attitude"]}/100 -4. **和Ta在别处的讨论摘要**: -{context["cross_context_block"]} -5. **最近的聊天摘要**: -{context["recent_chat_history"]} -""" - elif chat_type == "group": - group_info = context["group_info"] - prompt += f""" -# 任务 -现在是 {context["current_time"]},你需要根据当前的情境,决定是否要主动向群聊 '{group_info["group_name"]}' 发起对话。 - -# 情境分析 -1. **启动模式**: {start_mode} ({"首次加入/很久未发言" if start_mode == "cold_start" else "日常唤醒"}) -2. **你的日程**: -{context["schedule_context"]} -3. **群聊信息**: - - 群名称: {group_info["group_name"]} -4. **最近的聊天摘要**: -{context["recent_chat_history"]} -""" - # 构建通用尾部 - prompt += """ -# 决策目标 -你的最终目标是根据你的角色和当前情境,做出一个最符合人类社交直觉的决策,以求: -- **(私聊)深化关系**: 通过展现你的关心、记忆和个性来拉近与对方的距离。 -- **(群聊)活跃气氛**: 提出能引起大家兴趣的话题,促进群聊的互动。 -- **提供价值**: 你的出现应该是有意义的,无论是情感上的温暖,还是信息上的帮助。 -- **保持自然**: 避免任何看起来像机器人或骚扰的行为。 - -# 决策指令 -请综合以上所有信息,以稳定、真实、拟人的方式做出决策。你的决策需要以JSON格式输出,包含以下字段: -- `should_reply`: bool, 是否应该发起对话。 -- `topic`: str, 如果 `should_reply` 为 true,你打算聊什么话题? -- `reason`: str, 做出此决策的简要理由,需体现你对上述目标的考量。 - -# 决策流程与核心原则 -1. **检查对话状态**: - - **最后发言者**: 查看【最近的聊天摘要】。如果最后一条消息是你发的,且对方尚未回复,**通常应选择不回复**。这是最重要的原则,以避免打扰。 - - **例外**: 只有在等待时间足够长(例如超过数小时),或者你有非常重要且有时效性的新话题(例如,“你昨晚说的那个电影我刚看了!”)时,才考虑再次发言。 - - **无人发言**: 如果最近的聊天记录里只有你一个人在说话,**绝对不要回复**,以防刷屏。 - -2. **寻找话题切入点 (如果可以回复)**: - - **强关联优先**: 优先从【情境分析】中寻找最自然、最相关的话题。顺序建议:`最近的聊天摘要` > `你和Ta的关系` > `你的日程`。一个好的话题往往是对最近对话的延续。 - - **展现个性**: 结合你的【人设】和【情绪】,思考你会如何看待这些情境信息,并从中找到话题。例如,如果你是一个活泼的人,看到对方日程很满,可以说:“看你今天日程满满,真是活力四射的一天呀!” - - **备选方案**: 如果实在没有强关联的话题,可以发起一个简单的日常问候,如“在吗?”或“下午好”。 - -3. **最终决策**: - - **权衡频率**: 查看【你最近的相关决策历史】。如果你在短时间内已经主动发起过多次对话,即使现在有话题,也应倾向于**不回复**,保持一定的社交距离。 - - **质量胜于数量**: 宁可错过一次普通的互动机会,也不要进行一次尴尬或生硬的对话。 - - ---- -示例1 (基于上下文): -{{ - "should_reply": true, - "topic": "关心一下Ta昨天提到的那个项目进展如何了", - "reason": "用户昨天在聊天中提到了一个重要的项目,现在主动关心一下进展,会显得很体贴,也能自然地开启对话。" -}} - -示例2 (简单问候): -{{ - "should_reply": true, - "topic": "打个招呼,问问Ta现在在忙些什么", - "reason": "最近没有聊天记录,日程也很常规,没有特别的切入点。一个简单的日常问候是最安全和自然的方式来重新连接。" -}} - -示例3 (不应回复 - 过于频繁): -{{ - "should_reply": false, - "topic": null, - "reason": "虽然群里很活跃,但现在是深夜,而且最近的聊天话题我也不熟悉,没有合适的理由去打扰大家。" -}} - -示例4 (不应回复 - 等待回应): -{{ - "should_reply": false, - "topic": null, - "reason": "我注意到上一条消息是我几分钟前主动发送的,对方可能正在忙。为了表现出耐心和体贴,我现在最好保持安静,等待对方的回应。" -}} ---- - -请输出你的决策: -""" - return prompt - async def _make_decision(self, context: dict[str, Any], start_mode: str) -> dict[str, Any] | None: """ 调用 LLM 进行决策,判断是否应该主动发起对话,以及聊什么话题。 - - Args: - context: 包含所有上下文信息的字典。 - start_mode: 启动模式。 - - Returns: - 一个包含决策结果的字典 (例如: {"should_reply": bool, "topic": str, "reason": str}), - 如果决策过程失败则返回 None 或包含错误信息的字典。 """ if context["chat_type"] not in ["private", "group"]: return {"should_reply": False, "reason": "未知的聊天类型"} - prompt = self._build_decision_prompt(context, start_mode) + # 根据聊天类型构建特定上下文 + if context["chat_type"] == "private": + user_info = context["user_info"] + relationship = context["relationship"] + target_user_or_group = f"用户 '{user_info.user_nickname}'" + context_specific_block = f""" + 1. **启动模式**: {start_mode} ({"初次见面/很久未见" if start_mode == "cold_start" else "日常唤醒"}) + 2. **你的日程**: + {context["schedule_context"]} + 3. **你和Ta的关系**: + - 简短印象: {relationship["short_impression"]} + - 详细印象: {relationship["impression"]} + - 好感度: {relationship["attitude"]}/100 + 4. **和Ta在别处的讨论摘要**: + {context["cross_context_block"]} + 5. **最近的聊天摘要**: + {context["recent_chat_history"]} + """ + else: # group + group_info = context["group_info"] + target_user_or_group = f"群聊 '{group_info['group_name']}'" + context_specific_block = f""" + 1. **启动模式**: {start_mode} ({"首次加入/很久未发言" if start_mode == "cold_start" else "日常唤醒"}) + 2. **你的日程**: + {context["schedule_context"]} + 3. **群聊信息**: + - 群名称: {group_info["group_name"]} + 4. **最近的聊天摘要**: + {context["recent_chat_history"]} + """ + prompt = DECISION_PROMPT.format( + bot_nickname=global_config.bot.nickname, + persona_core=context["persona"]["core"], + persona_side=context["persona"]["side"], + identity=context["persona"]["identity"], + mood_state=context["mood_state"], + action_history_context=context["action_history_context"], + current_time=context["current_time"], + target_user_or_group=target_user_or_group, + context_specific_block=context_specific_block, + ) if global_config.debug.show_prompt: logger.info(f"主动思考决策器原始提示词:{prompt}") @@ -385,160 +334,3 @@ class ProactiveThinkerExecutor: except orjson.JSONDecodeError: logger.error(f"决策LLM返回的JSON格式无效: {response}") return {"should_reply": False, "reason": "决策模型返回格式错误"} - - def _build_private_plan_prompt(self, context: dict[str, Any], start_mode: str, topic: str, reason: str) -> str: - """ - 为私聊场景构建生成对话内容的规划提示词。 - - Args: - context: 上下文信息字典。 - start_mode: 启动模式。 - topic: 决策模块决定的话题。 - reason: 决策模块给出的理由。 - - Returns: - 构建完成的私聊规划提示词字符串。 - """ - user_info = context["user_info"] - relationship = context["relationship"] - if start_mode == "cold_start": - return f""" -# 任务 -你需要主动向一个新朋友 '{user_info.user_nickname}' 发起对话。这是你们的第一次交流,或者很久没聊了。 - -# 决策上下文 -- **决策理由**: {reason} - -# 情境分析 -1. **你的日程**: -{context["schedule_context"]} -2. **你和Ta的关系**: - - 简短印象: {relationship["short_impression"]} - - 详细印象: {relationship["impression"]} - - 好感度: {relationship["attitude"]}/100 -3. **和Ta在别处的讨论摘要**: -{context["cross_context_block"]} -4. **最近的聊天摘要**: -{context["recent_chat_history"]} -5. **你最近的相关动作**: -{context["action_history_context"]} - -# 对话指引 -- 你的目标是“破冰”,让对话自然地开始。 -- 你应该围绕这个话题展开: {topic} -- 你的语气应该符合你的人设和你当前的心情({context["mood_state"]}),友好且真诚。 -""" - else: # wake_up - return f""" -# 任务 -现在是 {context["current_time"]},你需要主动向你的朋友 '{user_info.user_nickname}' 发起对话。 - -# 决策上下文 -- **决策理由**: {reason} - -# 情境分析 -1. **你的日程**: -{context["schedule_context"]} -2. **你和Ta的关系**: - - 详细印象: {relationship["impression"]} - - 好感度: {relationship["attitude"]}/100 -3. **最近的聊天摘要**: -{context["recent_chat_history"]} -4. **你最近的相关动作**: -{context["action_history_context"]} - -# 对话指引 -- 你决定和Ta聊聊关于“{topic}”的话题。 -- **对话风格**: - - **自然开场**: 你可以根据话题和情境,选择最自然的开场方式。可以直接切入话题(如果话题关联性很强),也可以先用一句简单的问候(如“在吗?”、“下午好”)作为过渡。**不要总是使用同一种开场白**。 - - **融合情境**: 将【情境分析】中的信息(如你的心情、日程、对Ta的印象)巧妙地融入到对话中,让你的话语听起来更真实、更有依据。 - - **符合人设**: 你的语气、用词、甚至表情符号的使用,都应该完全符合你的【角色】设定和当前【情绪】({context["mood_state"]})以及你对Ta的好感度。 -- 请结合以上所有情境信息,自然地开启对话。 -""" - - def _build_group_plan_prompt(self, context: dict[str, Any], topic: str, reason: str) -> str: - """ - 为群聊场景构建生成对话内容的规划提示词。 - - Args: - context: 上下文信息字典。 - topic: 决策模块决定的话题。 - reason: 决策模块给出的理由。 - - Returns: - 构建完成的群聊规划提示词字符串。 - """ - group_info = context["group_info"] - return f""" -# 任务 -现在是 {context["current_time"]},你需要主动向群聊 '{group_info["group_name"]}' 发起对话。 - -# 决策上下文 -- **决策理由**: {reason} - -# 情境分析 -1. **你的日程**: -你当前的心情({context["mood_state"]} -{context["schedule_context"]} -2. **群聊信息**: - - 群名称: {group_info["group_name"]} -3. **最近的聊天摘要**: -{context["recent_chat_history"]} -4. **你最近的相关动作**: -{context["action_history_context"]} - -# 对话指引 -- 你决定和大家聊聊关于“{topic}”的话题。 -- **对话风格**: - - **自然开场**: 你可以根据话题和情境,选择最自然的开场方式。可以直接切入话题(如果话题关联性很强),也可以先用一句简单的问候(如“哈喽,大家好呀~”、“下午好!”)作为过渡。**不要总是使用同一种开场白**。 - - **融合情境**: 将【情境分析】中的信息(如你的心情、日程)巧妙地融入到对话中,让你的话语听起来更真实、更有依据。 - - **符合人设**: 你的语气、用词、甚至表情符号的使用,都应该完全符合你的【角色】设定和当前【情绪】({context["mood_state"]})。语气应该更活泼、更具包容性,以吸引更多群成员参与讨论。 -- 请结合以上所有情境信息,自然地开启对话。 -- 可以分享你的看法、提出相关问题,或者开个合适的玩笑。 -""" - - def _build_plan_prompt(self, context: dict[str, Any], start_mode: str, topic: str, reason: str) -> str: - """ - 根据聊天类型、启动模式和决策结果,构建最终生成对话内容的规划提示词。 - - Args: - context: 上下文信息字典。 - start_mode: 启动模式。 - topic: 决策模块决定的话题。 - reason: 决策模块给出的理由。 - - Returns: - 最终的规划提示词字符串。 - """ - persona = context["persona"] - chat_type = context["chat_type"] - - # 1. 构建通用角色头部 - prompt = f""" -# 角色 -你的名字是{global_config.bot.nickname},你的人设如下: -- 核心人设: {persona["core"]} -- 侧面人设: {persona["side"]} -- 身份: {persona["identity"]} -""" - # 2. 根据聊天类型构建特定内容 - if chat_type == "private": - prompt += self._build_private_plan_prompt(context, start_mode, topic, reason) - elif chat_type == "group": - prompt += self._build_group_plan_prompt(context, topic, reason) - - # 3. 添加通用结尾 - final_instructions = """ - -# 输出要求 -- **简洁**: 不要输出任何多余内容(如前缀、后缀、冒号、引号、at/@等)。 -- **原创**: 不要重复之前的内容,即使意思相近也不行。 -- **直接**: 只输出最终的回复文本本身。 -- **风格**: 回复需简短、完整且口语化。 - -现在,你说:""" - prompt += final_instructions - - if global_config.debug.show_prompt: - logger.info(f"主动思考回复器原始提示词:{prompt}") - return prompt diff --git a/src/plugins/built_in/proactive_thinker/prompts.py b/src/plugins/built_in/proactive_thinker/prompts.py new file mode 100644 index 000000000..eff355aad --- /dev/null +++ b/src/plugins/built_in/proactive_thinker/prompts.py @@ -0,0 +1,97 @@ +from src.chat.utils.prompt import Prompt + +# ============================================================================= +# 决策阶段 (Decision Phase) +# ============================================================================= + +DECISION_PROMPT = Prompt( + name="proactive_thinker_decision", + template=""" +# 角色 +你的名字是{bot_nickname},你的人设如下: +- 核心人设: {persona_core} +- 侧面人设: {persona_side} +- 身份: {identity} + +你的当前情绪状态是: {mood_state} + +# 你最近的相关决策历史 (供参考) +{action_history_context} + +# 任务 +现在是 {current_time},你需要根据当前的情境,决定是否要主动向{target_user_or_group}发起对话。 + +# 情境分析 +{context_specific_block} + +# 决策目标 +你的最终目标是根据你的角色和当前情境,做出一个最符合人类社交直觉的决策,以求: +- **(私聊)深化关系**: 通过展现你的关心、记忆和个性来拉近与对方的距离。 +- **(群聊)活跃气氛**: 提出能引起大家兴趣的话题,促进群聊的互动。 +- **提供价值**: 你的出现应该是有意义的,无论是情感上的温暖,还是信息上的帮助。 +- **保持自然**: 避免任何看起来像机器人或骚扰的行为。 + +# 决策指令 +请综合以上所有信息,以稳定、真实、拟人的方式做出决策。你的决策需要以JSON格式输出,包含以下字段: +- `should_reply`: bool, 是否应该发起对话。 +- `topic`: str, 如果 `should_reply` 为 true,你打算聊什么话题? +- `reason`: str, 做出此决策的简要理由,需体现你对上述目标的考量。 + +# 决策流程与核心原则 +1. **检查对话状态**: + - **最后发言者**: 查看【最近的聊天摘要】。如果最后一条消息是你发的,且对方尚未回复,**通常应选择不回复**。这是最重要的原则,以避免打扰。 + - **例外**: 只有在等待时间足够长(例如超过数小时),或者你有非常重要且有时效性的新话题时,才考虑再次发言。 + - **无人发言**: 如果最近的聊天记录里只有你一个人在说话,**绝对不要回复**,以防刷屏。 + +2. **寻找话题切入点 (如果可以回复)**: + - **强关联优先**: 优先从【情境分析】中寻找最自然、最相关的话题。顺序建议:`最近的聊天摘要` > `你和Ta的关系` > `你的日程`。 + - **展现个性**: 结合你的【人设】和【情绪】,思考你会如何看待这些情境信息,并从中找到话题。 + - **备选方案**: 如果实在没有强关联的话题,可以发起一个简单的日常问候。 + +3. **最终决策**: + - **权衡频率**: 查看【你最近的相关决策历史】。如果你在短时间内已经主动发起过多次对话,也应倾向于**不回复**,保持一定的社交距离。 + - **质量胜于数量**: 宁可错过一次普通的互动机会,也不要进行一次尴尬或生硬的对话。 + +--- +请输出你的决策: +""" +) + +# ============================================================================= +# 回复规划阶段 (Plan Phase) +# ============================================================================= + +PLAN_PROMPT = Prompt( + name="proactive_thinker_plan", + template=""" +# 角色 +你的名字是{bot_nickname},你的人设如下: +- 核心人设: {persona_core} +- 侧面人设: {persona_side} +- 身份: {identity} + +# 任务 +现在是 {current_time},你需要主动向{target_user_or_group}发起对话。 + +# 决策上下文 +- **决策理由**: {reason} + +# 情境分析 +{context_specific_block} + +# 对话指引 +- 你决定和Ta聊聊关于“{topic}”的话题。 +- **对话风格**: + - **自然开场**: 你可以根据话题和情境,选择最自然的开场方式。可以直接切入话题(如果话题关联性很强),也可以先用一句简单的问候作为过渡。**不要总是使用同一种开场白**。 + - **融合情境**: 将【情境分析】中的信息巧妙地融入到对话中,让你的话语听起来更真实、更有依据。 + - **符合人设**: 你的语气、用词、甚至表情符号的使用,都应该完全符合你的【角色】设定和当前【情绪】({mood_state})。 + +# 输出要求 +- **简洁**: 不要输出任何多余内容(如前缀、后缀、冒号、引号、at/@等)。 +- **原创**: 不要重复之前的内容,即使意思相近也不行。 +- **直接**: 只输出最终的回复文本本身。 +- **风格**: 回复需简短、完整且口语化。 + +现在,你说: +""" +) \ No newline at end of file