refactor(proactive_thinker): 将提示词构建逻辑迁移至独立的prompt模块

将 `ProactiveThinkerExecutor` 中用于构建决策(decision)和计划(plan)的提示词字符串的逻辑,提取并重构到新的 `prompts.py` 模块中。

这一重构旨在:
- **提升可维护性**:将复杂的提示词模板与核心业务逻辑分离,使代码结构更清晰。
- **简化代码**:`proactive_thinker_executor.py` 的代码量大幅减少,职责更加单一,专注于执行流程。
- **便于管理**:集中管理所有相关的提示词模板,方便未来进行统一的调整和优化。
This commit is contained in:
minecraft1024a
2025-10-25 09:30:20 +08:00
parent f90205feaa
commit cbad858026
2 changed files with 184 additions and 295 deletions

View File

@@ -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

View File

@@ -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/@等)。
- **原创**: 不要重复之前的内容,即使意思相近也不行。
- **直接**: 只输出最终的回复文本本身。
- **风格**: 回复需简短、完整且口语化。
现在,你说:
"""
)