refactor(proactive_thinker): 重构上下文收集与提示词构建逻辑以支持群聊

将原有的上下文收集函数 `_gather_context` 和提示词构建函数 `_make_decision`, `_build_plan_prompt` 拆分为更细粒度的模块,以分别处理私聊和群聊场景。

主要变更:
- `_gather_context`: 现在能根据聊天流是群聊还是私聊,收集不同的上下文信息,并添加 `chat_type` 字段以作区分。
- `_build_decision_prompt`: 新增函数,根据 `chat_type` 构建不同的决策提示词,使决策更贴合场景。
- `_build_plan_prompt`: 重构为调度函数,内部调用新增的 `_build_private_plan_prompt` 和 `_build_group_plan_prompt` 来生成特定场景的规划提示词。
- 整体逻辑更加清晰,增强了代码的可读性和可扩展性,为未来支持更多聊天类型奠定了基础。
This commit is contained in:
minecraft1024a
2025-10-03 17:15:34 +08:00
parent a2bbb8041a
commit 275157c58a

View File

@@ -104,61 +104,31 @@ class ProactiveThinkerExecutor:
async def _gather_context(self, stream_id: str) -> dict[str, Any] | None:
"""
收集构建提示词所需的所有上下文信息
收集构建提示词所需的所有上下文信息.
根据聊天流是私聊还是群聊, 收集不同的上下文.
"""
stream = self._get_stream_from_id(stream_id)
if not stream:
logger.warning(f"无法找到 stream_id 为 {stream_id} 的聊天流")
return None
user_info = stream.user_info
if not user_info or not user_info.platform or not user_info.user_id:
logger.warning(f"Stream {stream_id} 的 user_info 不完整")
return None
person_id = person_api.get_person_id(user_info.platform, int(user_info.user_id))
person_info_manager = get_person_info_manager()
# 获取日程
# 1. 收集通用信息 (日程, 聊天历史, 动作历史)
schedules = await schedule_api.ScheduleAPI.get_today_schedule()
schedule_context = (
"\n".join(
[
f"- {s.get('time_range', '未知时间')}: {s.get('activity', '未知活动')}"
for s in schedules
]
)
if schedules
else "今天没有日程安排。"
)
# 获取关系信息
short_impression = await person_info_manager.get_value(person_id, "short_impression") or ""
impression = await person_info_manager.get_value(person_id, "impression") or ""
attitude = await person_info_manager.get_value(person_id, "attitude") or 50
# 获取最近聊天记录
schedule_context = "\n".join([f"- {s.get('time_range', '未知时间')}: {s.get('activity', '未知活动')}" for s in schedules]) if schedules else "今天没有日程安排。"
recent_messages = await message_api.get_recent_messages(stream_id, limit=10)
recent_chat_history = (
await message_api.build_readable_messages_to_str(recent_messages) if recent_messages else ""
)
# 获取最近的动作历史
action_history = await database_api.db_query(
recent_chat_history = await message_api.build_readable_messages_to_str(recent_messages) if recent_messages else ""
action_history_list = await database_api.db_query(
database_api.MODEL_MAPPING["ActionRecords"],
filters={"chat_id": stream_id, "action_name": "proactive_decision"},
limit=3,
order_by=["-time"],
)
action_history_context = ""
if isinstance(action_history, list):
action_history_context = (
"\n".join([f"- {a['action_data']}" for a in action_history if isinstance(a, dict)]) or ""
)
action_history_context = "\n".join([f"- {a['action_data']}" for a in action_history_list if isinstance(a, dict)]) if isinstance(action_history_list, list) else ""
return {
"person_id": person_id,
"user_info": user_info,
# 2. 构建基础上下文
base_context = {
"schedule_context": schedule_context,
"recent_chat_history": recent_chat_history,
"action_history_context": action_history_context,
@@ -170,21 +140,63 @@ class ProactiveThinkerExecutor:
"current_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
async def _make_decision(self, context: dict[str, Any], start_mode: str) -> dict[str, Any] | None:
"""
决策模块:判断是否应该主动发起对话,以及聊什么话题
"""
persona = context["persona"]
user_info = context["user_info"]
relationship = context["relationship"]
# 3. 根据聊天类型补充特定上下文
if stream.group_info: # 群聊场景
base_context.update({
"chat_type": "group",
"group_info": {
"group_name": stream.group_info.group_name,
"group_id": stream.group_info.group_id
}
})
return base_context
elif stream.user_info: # 私聊场景
user_info = stream.user_info
if not user_info.platform or not user_info.user_id:
logger.warning(f"Stream {stream_id} 的 user_info 不完整")
return None
person_id = person_api.get_person_id(user_info.platform, int(user_info.user_id))
person_info_manager = get_person_info_manager()
# 获取关系信息
short_impression = await person_info_manager.get_value(person_id, "short_impression") or ""
impression = await person_info_manager.get_value(person_id, "impression") or ""
attitude = await person_info_manager.get_value(person_id, "attitude") or 50
base_context.update({
"chat_type": "private",
"person_id": person_id,
"user_info": user_info,
"relationship": {
"short_impression": short_impression,
"impression": impression,
"attitude": attitude
}
})
return base_context
else:
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:
""" 根据上下文构建决策prompt """
chat_type = context["chat_type"]
persona = context["persona"]
# 构建通用头部
prompt = f"""
# 角色
你的名字是{global_config.bot.nickname},你的人设如下:
- 核心人设: {persona["core"]}
- 侧面人设: {persona["side"]}
- 身份: {persona["identity"]}
"""
# 根据聊天类型构建任务和情境
if chat_type == "private":
user_info = context["user_info"]
relationship = context["relationship"]
prompt += f"""
# 任务
现在是 {context["current_time"]},你需要根据当前的情境,决定是否要主动向用户 '{user_info.user_nickname}' 发起对话。
@@ -198,7 +210,24 @@ class ProactiveThinkerExecutor:
- 好感度: {relationship["attitude"]}/100
4. **最近的聊天摘要**:
{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, 是否应该发起对话。
@@ -209,22 +238,34 @@ class ProactiveThinkerExecutor:
示例1 (应该回复):
{{
"should_reply": true,
"topic": "提醒Ta今天下午有'项目会议'的日程",
"reason": "现在是上午,Ta下午有个重要会议,我觉得应该主动提醒一下,这会显得我很贴心。"
"topic": "提醒大家今天下午有'项目会议'的日程",
"reason": "现在是上午,下午有个重要会议,我觉得应该主动提醒一下大家,这会显得我很贴心。"
}}
示例2 (不应回复):
{{
"should_reply": false,
"topic": null,
"reason": "虽然我们的关系不错,但现在是深夜,而且Ta今天的日程都已经完成了没有合适的理由去打扰Ta"
"reason": "虽然群里很活跃,但现在是深夜,而且最近的聊天话题我也不熟悉,没有合适的理由去打扰大家"
}}
---
请输出你的决策:
"""
return prompt
async def _make_decision(self, context: dict[str, Any], start_mode: str) -> dict[str, Any] | None:
"""
决策模块:判断是否应该主动发起对话,以及聊什么话题
"""
if context["chat_type"] not in ["private", "group"]:
return {"should_reply": False, "reason": "未知的聊天类型"}
prompt = self._build_decision_prompt(context, start_mode)
if global_config.debug.show_prompt:
logger.info(f"主动思考规划器原始提示词:{prompt}")
logger.info(f"主动思考决策器原始提示词:{prompt}")
is_success, response, _, _ = await llm_api.generate_with_model(
prompt=prompt, model_config=model_config.model_task_config.utils
)
@@ -233,31 +274,21 @@ class ProactiveThinkerExecutor:
return {"should_reply": False, "reason": "决策模型生成失败"}
try:
# 假设LLM返回JSON格式的决策结果
if global_config.debug.show_prompt:
logger.info(f"主动思考规划器响应:{response}")
logger.info(f"主动思考决策器响应:{response}")
decision = orjson.loads(response)
return decision
except orjson.JSONDecodeError:
logger.error(f"决策LLM返回的JSON格式无效: {response}")
return {"should_reply": False, "reason": "决策模型返回格式错误"}
def _build_plan_prompt(self, context: dict[str, Any], start_mode: str, topic: str, reason: str) -> str:
"""
根据启动模式和决策话题,构建最终的规划提示词
"""
persona = context["persona"]
def _build_private_plan_prompt(self, context: dict[str, Any], start_mode: str, topic: str, reason: str) -> str:
""" 构建私聊的规划Prompt """
user_info = context["user_info"]
relationship = context["relationship"]
if start_mode == "cold_start":
prompt = f"""
# 角色
你的名字是{global_config.bot.nickname},你的人设如下:
- 核心人设: {persona["core"]}
- 侧面人设: {persona["side"]}
- 身份: {persona["identity"]}
return f"""
# 任务
你需要主动向一个新朋友 '{user_info.user_nickname}' 发起对话。这是你们的第一次交流,或者很久没聊了。
@@ -272,16 +303,9 @@ class ProactiveThinkerExecutor:
- 你的目标是“破冰”,让对话自然地开始。
- 你应该围绕这个话题展开: {topic}
- 你的语气应该符合你的人设,友好且真诚。
- 直接输出你要说的第一句话,不要包含任何额外的前缀或解释。
"""
else: # wake_up
prompt = f"""
# 角色
你的名字是{global_config.bot.nickname},你的人设如下:
- 核心人设: {persona["core"]}
- 侧面人设: {persona["side"]}
- 身份: {persona["identity"]}
return f"""
# 任务
现在是 {context["current_time"]},你需要主动向你的朋友 '{user_info.user_nickname}' 发起对话。
@@ -303,8 +327,58 @@ class ProactiveThinkerExecutor:
- 你决定和Ta聊聊关于“{topic}”的话题。
- 请结合以上所有情境信息,自然地开启对话。
- 你的语气应该符合你的人设以及你对Ta的好感度。
- 直接输出你要说的第一句话,不要包含任何额外的前缀或解释。
"""
def _build_group_plan_prompt(self, context: dict[str, Any], topic: str, reason: str) -> str:
""" 构建群聊的规划Prompt """
group_info = context["group_info"]
return f"""
# 任务
现在是 {context["current_time"]},你需要主动向群聊 '{group_info["group_name"]}' 发起对话。
# 决策上下文
- **决策理由**: {reason}
# 情境分析
1. **你的日程**:
{context["schedule_context"]}
2. **群聊信息**:
- 群名称: {group_info["group_name"]}
3. **最近的聊天摘要**:
{context["recent_chat_history"]}
4. **你最近的相关动作**:
{context["action_history_context"]}
# 对话指引
- 你决定和大家聊聊关于“{topic}”的话题。
- 你的语气应该更活泼、更具包容性,以吸引更多群成员参与讨论。
- 请结合以上所有情境信息,自然地开启对话。
"""
def _build_plan_prompt(self, context: dict[str, Any], start_mode: str, topic: str, reason: str) -> str:
"""
根据启动模式和决策话题,构建最终的规划提示词
"""
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. 添加通用结尾
prompt += "\n- 直接输出你要说的第一句话,不要包含任何额外的前缀或解释。"
if global_config.debug.show_prompt:
logger.info(f"主动思考回复器原始提示词:{prompt}")
return prompt