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