再推!

pull/937/head
114514 2025-05-07 08:03:03 +08:00
parent 377d798c72
commit 67b958a140
3 changed files with 339 additions and 165 deletions

View File

@ -124,6 +124,48 @@ PROMPT_END_DECISION = """
注意请严格按照 JSON 格式输出不要包含任何其他内容"""
# Prompt(4): 当 reply_generator 决定不发送消息后的反思决策 Prompt
PROMPT_REFLECT_AND_ACT = """
当前时间{current_time_str}
{persona_text}
现在你正在和{sender_name}在QQ上私聊
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
刚刚你本来想发一条新消息但是想了想你决定不发了
请根据以下所有信息审慎且灵活的决策下一步行动可以等待可以倾听可以结束对话甚至可以屏蔽对方
当前对话目标
{goals_str}
最近行动历史概要
{action_history_summary}
你想起来的相关知识
{retrieved_knowledge_str}
上一次行动的详细情况和结果
{last_action_context}
时间和超时提示
{time_since_last_bot_message_info}{timeout_context}
最近的对话记录(包括你已成功发送的消息 新收到的消息)
{chat_history_text}
你的回忆
{retrieved_memory_str}
{spam_warning_info}
------
可选行动类型以及解释
wait: 等待暂时不说话
listening: 倾听对方发言虽然你刚发过言但如果对方立刻回复且明显话没说完可以选择这个
rethink_goal: 思考一个对话目标当你觉得目前对话需要目标或当前目标不再适用或话题卡住时选择注意私聊的环境是灵活的有可能需要经常选择
end_conversation: 安全和平的结束对话对方长时间没回复繁忙已经不再回复你消息明显暗示或表达想结束聊天时可以果断选择
block_and_ignore: 更加极端的结束对话方式直接结束对话并在一段时间内无视对方所有发言屏蔽当对话让你感到十分不适或你遭到各类骚扰时选择
请以JSON格式输出你的决策
{{
"action": "选择的行动类型 (必须是上面列表中的一个)",
"reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的。)"
}}
注意请严格按照JSON格式输出不要包含任何其他内容"""
class ActionPlanner:
"""行动规划器"""
@ -162,6 +204,7 @@ class ActionPlanner:
observation_info: ObservationInfo,
conversation_info: ConversationInfo,
last_successful_reply_action: Optional[str],
use_reflect_prompt: bool = False # 新增参数用于指示是否使用PROMPT_REFLECT_AND_ACT
) -> Tuple[str, str]:
"""
规划下一步行动
@ -206,24 +249,38 @@ class ActionPlanner:
# --- 2. 选择并格式化 Prompt ---
try:
if last_successful_reply_action in ["direct_reply", "send_new_message"]:
if use_reflect_prompt: # 新增的判断
prompt_template = PROMPT_REFLECT_AND_ACT
log_msg = "使用 PROMPT_REFLECT_AND_ACT (反思决策)"
# 对于 PROMPT_REFLECT_AND_ACT它不包含 send_new_message 选项,所以 spam_warning_message 中的相关提示可以调整或省略
# 但为了保持占位符填充的一致性,我们仍然计算它
spam_warning_message = ""
if conversation_info.my_message_count > 5: # 这里的 my_message_count 仍有意义,表示之前连续发送了多少
spam_warning_message = f"⚠️【警告】**你之前已连续发送{str(conversation_info.my_message_count)}条消息!请谨慎决策。**"
elif conversation_info.my_message_count > 2:
spam_warning_message = f"💬【提示】**你之前已连续发送{str(conversation_info.my_message_count)}条消息。请注意保持对话平衡。**"
elif last_successful_reply_action in ["direct_reply", "send_new_message"]:
prompt_template = PROMPT_FOLLOW_UP
log_msg = "使用 PROMPT_FOLLOW_UP (追问决策)"
spam_warning_message = ""
if conversation_info.my_message_count > 5:
spam_warning_message = f"⚠️【警告】**你已连续发送{str(conversation_info.my_message_count)}条消息请注意不要再选择send_new_message以免刷屏对造成对方困扰**"
elif conversation_info.my_message_count > 2:
spam_warning_message = f"💬【警告】**你已连续发送{str(conversation_info.my_message_count)}条消息。请保持理智如果非必要请避免选择send_new_message以免给对方造成困扰。**"
else:
prompt_template = PROMPT_INITIAL_REPLY
log_msg = "使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)"
spam_warning_message = "" # 初始回复时通常不需要刷屏警告
logger.debug(f"[私聊][{self.private_name}] {log_msg}")
current_time_value = "获取时间失败" # 默认值
current_time_value = "获取时间失败"
if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str:
current_time_value = observation_info.current_time_str
spam_warning_message = "" # 初始化为空字符串
if conversation_info.my_message_count > 5:
spam_warning_message = f"⚠️【警告】**你已连续发送{str(conversation_info.my_message_count)}条消息请注意不要再选择send_new_message以免刷屏对造成对方困扰**"
elif conversation_info.my_message_count > 2:
spam_warning_message = f"💬【警告】**你已连续发送{str(conversation_info.my_message_count)}条消息。请保持理智如果非必要请避免选择send_new_message以免给对方造成困扰。**"
if spam_warning_message: # 仅当有警告时才添加换行符
if spam_warning_message:
spam_warning_message = f"\n{spam_warning_message}\n"
prompt = prompt_template.format(
@ -236,9 +293,8 @@ class ActionPlanner:
chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。",
retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。",
retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。",
current_time_str=current_time_value, # 新增:传入当前时间字符串
current_time_str=current_time_value,
spam_warning_info=spam_warning_message,
### 标记新增/修改区域 开始 ###
sender_name=sender_name_str,
relationship_text=relationship_text_str,
current_emotion_text=current_emotion_text_str
@ -299,7 +355,7 @@ class ActionPlanner:
# final_reason = initial_reason
# --- 5. 验证最终行动类型 ---
valid_actions = [
valid_actions_default = [
"direct_reply",
"send_new_message",
"wait",
@ -309,7 +365,19 @@ class ActionPlanner:
"block_and_ignore",
"say_goodbye",
]
if final_action not in valid_actions:
valid_actions_reflect = [ # PROMPT_REFLECT_AND_ACT 的动作
"wait",
"listening",
"rethink_goal",
"end_conversation",
"block_and_ignore",
# PROMPT_REFLECT_AND_ACT 也可以 end_conversation然后也可能触发 say_goodbye
"say_goodbye",
]
current_valid_actions = valid_actions_reflect if use_reflect_prompt else valid_actions_default
if final_action not in current_valid_actions:
logger.warning(f"[私聊][{self.private_name}] LLM 返回了未知的行动类型: '{final_action}',强制改为 wait")
final_reason = f"(原始行动'{final_action}'无效已强制改为wait) {final_reason}"
final_action = "wait" # 遇到无效动作,默认等待

View File

@ -2,6 +2,8 @@ import time
import asyncio
import datetime
import traceback
import json # 确保导入 json 模块
from .pfc_utils import get_items_from_json # 导入JSON解析工具
from typing import Dict, Any, Optional, Set, List
from dateutil import tz
@ -506,7 +508,7 @@ class Conversation:
if not self._initialized:
logger.error(f"[私聊][{self.private_name}] 尝试在未初始化状态下运行规划循环,退出。")
return # 明确退出
force_reflect_and_act = False
# 主循环,只要 should_continue 为 True 就一直运行
while self.should_continue:
loop_iter_start_time = time.time() # 记录本次循环开始时间
@ -654,10 +656,12 @@ class Conversation:
logger.debug(f"[私聊][{self.private_name}] 调用 ActionPlanner.plan...")
# 传入当前观察信息、对话信息和上次成功回复的动作类型
action, reason = await self.action_planner.plan(
self.observation_info,
self.conversation_info,
self.conversation_info.last_successful_reply_action
self.observation_info,
self.conversation_info, # type: ignore
self.conversation_info.last_successful_reply_action, # type: ignore
use_reflect_prompt=force_reflect_and_act # 使用标志
)
force_reflect_and_act = False
planning_duration = time.time() - planning_start_time
logger.debug(
f"[私聊][{self.private_name}] ActionPlanner.plan 完成 (耗时: {planning_duration:.3f} 秒),初步规划动作: {action}"
@ -765,6 +769,16 @@ class Conversation:
await self._handle_action(action, reason, self.observation_info, self.conversation_info)
logger.debug(f"[私聊][{self.private_name}] _handle_action 完成。")
# --- 新增逻辑检查是否因为RG决定不发送而需要反思 ---
last_action_record = (
self.conversation_info.done_action[-1] if self.conversation_info.done_action else {} # type: ignore
)
if last_action_record.get("action") == "send_new_message" and \
last_action_record.get("status") == "done_no_reply":
logger.info(f"[私聊][{self.private_name}] 检测到 ReplyGenerator 决定不发送消息将在下一轮强制使用反思Prompt。")
force_reflect_and_act = True # 设置标志下一轮使用反思prompt
# 不需要立即 continue让循环自然进入下一轮下一轮的 plan 会用这个标志
# 8. 检查是否需要结束整个对话(例如目标达成或执行了结束动作)
goal_ended: bool = False
# 检查最新的目标是否是“结束对话”
@ -913,68 +927,105 @@ class Conversation:
check_reason: str = "未进行检查" # 存储检查结果原因
# --- [核心修复] 引入重试循环 ---
is_send_decision_from_rg = False # 标记是否由 reply_generator 决定发送
while reply_attempt_count < max_reply_attempts and not is_suitable and not need_replan_from_checker:
reply_attempt_count += 1
log_prefix = f"[私聊][{self.private_name}] 尝试生成/检查 '{action}' 回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)..."
logger.info(log_prefix)
# --- a. 生成回复 ---
self.state = ConversationState.GENERATING # 更新对话状态
self.state = ConversationState.GENERATING
if not self.reply_generator:
# 检查依赖组件是否存在
raise RuntimeError("ReplyGenerator 未初始化")
# 调用 ReplyGenerator 生成回复内容
generated_content = await self.reply_generator.generate(
raw_llm_output = await self.reply_generator.generate(
observation_info, conversation_info, action_type=action
)
logger.info(f"{log_prefix} 生成内容: '{generated_content}'") # 日志中截断长内容
logger.debug(f"{log_prefix} ReplyGenerator.generate 返回: '{raw_llm_output}'")
# 检查生成内容是否有效
if not generated_content or generated_content.startswith("抱歉"):
# 如果生成失败或返回错误提示
logger.warning(f"{log_prefix} 生成内容为空或为错误提示,将进行下一次尝试。")
check_reason = "生成内容无效" # 记录原因
# 记录拒绝信息供下次生成参考
should_send_reply = True # 默认对于 direct_reply 是要发送的
text_to_process = raw_llm_output # 默认情况下,处理原始输出
if action == "send_new_message":
is_send_decision_from_rg = True # 标记 send_new_message 的决策来自RG
try:
# 使用 pfc_utils.py 中的 get_items_from_json 来解析
# 注意get_items_from_json 目前主要用于提取固定字段的字典。
# reply_generator 返回的是一个顶级JSON对象。
# 我们需要稍微调整用法或增强 get_items_from_json。
# 简单起见,这里我们先直接用 json.loads后续可以优化。
parsed_json = None
try:
parsed_json = json.loads(raw_llm_output)
except json.JSONDecodeError:
logger.error(f"{log_prefix} ReplyGenerator 返回的不是有效的JSON: {raw_llm_output}")
# 如果JSON解析失败视为RG决定不发送并给出原因
conversation_info.last_reply_rejection_reason = "回复生成器未返回有效JSON"
conversation_info.last_rejected_reply_content = raw_llm_output
should_send_reply = False
text_to_process = "no" # 或者一个特定的错误标记
if parsed_json:
send_decision = parsed_json.get("send", "no").lower()
generated_text_from_json = parsed_json.get("txt", "no")
if send_decision == "yes":
should_send_reply = True
text_to_process = generated_text_from_json
logger.info(f"{log_prefix} ReplyGenerator 决定发送消息。内容: '{text_to_process[:100]}...'")
else:
should_send_reply = False
text_to_process = "no" # 保持和 prompt 中一致txt 为 "no"
logger.info(f"{log_prefix} ReplyGenerator 决定不发送消息。")
# 此时,我们应该跳出重试循环,并触发 action_planner 的反思 prompt
# 将此信息传递到循环外部进行处理
break # 跳出 while 循环
except Exception as e_json: # 更广泛地捕获解析相关的错误
logger.error(f"{log_prefix} 解析 ReplyGenerator 的JSON输出时出错: {e_json}, 输出: {raw_llm_output}")
conversation_info.last_reply_rejection_reason = f"解析回复生成器JSON输出错误: {e_json}"
conversation_info.last_rejected_reply_content = raw_llm_output
should_send_reply = False
text_to_process = "no"
if not should_send_reply and action == "send_new_message": # 如果RG决定不发送 (send_new_message特定逻辑)
break # 直接跳出重试循环,后续逻辑会处理这种情况
generated_content_for_check_or_send = text_to_process
if not generated_content_for_check_or_send or generated_content_for_check_or_send.startswith("抱歉") or (action == "send_new_message" and generated_content_for_check_or_send == "no"):
logger.warning(f"{log_prefix} 生成内容无效或为错误提示 (或send:no),将进行下一次尝试 (如果适用)。")
check_reason = "生成内容无效或选择不发送"
conversation_info.last_reply_rejection_reason = check_reason
conversation_info.last_rejected_reply_content = generated_content
await asyncio.sleep(0.5) # 短暂等待后重试
continue # 进入下一次循环尝试
conversation_info.last_rejected_reply_content = generated_content_for_check_or_send
if action == "direct_reply": # direct_reply 失败时才继续尝试
await asyncio.sleep(0.5)
continue
else: # send_new_message 如果是 no不应该继续尝试上面已经break了
pass # 理论上不会到这里如果上面break了
# --- b. 检查回复 ---
self.state = ConversationState.CHECKING # 更新状态为检查中
self.state = ConversationState.CHECKING
if not self.reply_checker:
raise RuntimeError("ReplyChecker 未初始化")
# 准备检查所需的上下文信息
current_goal_str: str = "" # 当前对话目标字符串
current_goal_str = ""
if conversation_info.goal_list:
# 通常检查最新的目标
goal_item = conversation_info.goal_list[-1]
if isinstance(goal_item, dict):
current_goal_str = goal_item.get("goal", "")
elif isinstance(goal_item, str):
current_goal_str = goal_item
# 获取用于检查的聊天记录 (列表和字符串形式)
chat_history_for_check: List[Dict[str, Any]] = getattr(observation_info, "chat_history", [])
chat_history_text_for_check: str = getattr(observation_info, "chat_history_str", "")
# 当前重试次数 (传递给 checker可能有用)
# retry_count for checker starts from 0
current_retry_for_checker = reply_attempt_count - 1
chat_history_for_check = getattr(observation_info, "chat_history", [])
chat_history_text_for_check = getattr(observation_info, "chat_history_str", "")
current_retry_for_checker = reply_attempt_count - 1
current_time_value_for_check = observation_info.current_time_str or "获取时间失败"
current_time_value_for_check = "获取时间失败"
if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str:
current_time_value_for_check = observation_info.current_time_str
logger.debug(f"{log_prefix} 调用 ReplyChecker 检查...")
# --- 根据配置决定是否执行检查 ---
if global_config.enable_pfc_reply_checker: # <--- 使用配置项
if global_config.enable_pfc_reply_checker:
logger.debug(f"{log_prefix} 调用 ReplyChecker 检查 (配置已启用)...")
is_suitable, check_reason, need_replan_from_checker = await self.reply_checker.check(
reply=generated_content,
reply=generated_content_for_check_or_send,
goal=current_goal_str,
chat_history=chat_history_for_check,
chat_history_text=chat_history_text_for_check,
@ -985,67 +1036,96 @@ class Conversation:
f"{log_prefix} ReplyChecker 结果: 合适={is_suitable}, 原因='{check_reason}', 需重规划={need_replan_from_checker}"
)
else:
# 如果配置为关闭,则默认通过检查
is_suitable = True
check_reason = "ReplyChecker 已通过配置关闭"
need_replan_from_checker = False
logger.info(f"[配置关闭] ReplyChecker 已跳过,默认回复为合适。")
# 如果不合适,记录原因并准备下一次尝试(如果还有次数)
logger.info(f"{log_prefix} [配置关闭] ReplyChecker 已跳过,默认回复为合适。")
if not is_suitable:
# 记录拒绝原因和内容,供下次生成时参考
conversation_info.last_reply_rejection_reason = check_reason
conversation_info.last_rejected_reply_content = generated_content
# 如果不需要重规划且还有尝试次数
conversation_info.last_rejected_reply_content = generated_content_for_check_or_send
if not need_replan_from_checker and reply_attempt_count < max_reply_attempts:
logger.warning(f"{log_prefix} 回复不合适,原因: {check_reason}。将进行下一次尝试。")
await asyncio.sleep(0.5) # 等待后重试
await asyncio.sleep(0.5)
# 如果需要重规划或达到最大次数,循环会在下次判断时自动结束
# --- 循环结束后处理 ---
if action == "send_new_message" and not should_send_reply and is_send_decision_from_rg:
# 这是 reply_generator 决定不发送的情况
logger.info(f"[私聊][{self.private_name}] 动作 '{action}': ReplyGenerator 决定不发送消息。将调用 ActionPlanner 进行反思。")
final_status = "done_no_reply" # 一个新的状态,表示动作完成但无回复
final_reason = "回复生成器决定不发送消息"
action_successful = True # 动作本身(决策)是成功的
# --- 循环结束后,处理最终结果 ---
if is_suitable:
# 如果找到了合适的回复
# 清除追问状态,因为没有实际发送
conversation_info.last_successful_reply_action = None
conversation_info.my_message_count = 0 # 重置连续发言计数
# !!! 触发 ActionPlanner 使用 PROMPT_REFLECT_AND_ACT !!!
if not self.action_planner:
raise RuntimeError("ActionPlanner 未初始化")
logger.info(f"[私聊][{self.private_name}] {self.name} 本来想发一条新消息,但是想想还是算了。现在重新规划...")
# 调用 action_planner.plan 并传入 use_reflect_prompt=True
new_action, new_reason = await self.action_planner.plan(
observation_info,
conversation_info,
last_successful_reply_action=None, # 因为没发送,所以没有成功的回复动作
use_reflect_prompt=True
)
# 记录这次特殊的“反思”动作
reflect_action_record = {
"action": f"reflect_after_no_send ({new_action})", # 记录原始意图和新规划
"plan_reason": f"RG决定不发送后AP规划: {new_reason}",
"status": "delegated", # 标记为委托给新的规划
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
conversation_info.done_action.append(reflect_action_record)
logger.info(f"[私聊][{self.private_name}] 反思后的新规划动作: {new_action}, 原因: {new_reason}")
# **暂定方案:**
# _handle_action 在这种情况下返回一个特殊标记。
# 为了不立即修改返回类型,我们暂时在这里记录日志,并在 _plan_and_action_loop 中添加逻辑。
# _handle_action 会将 action_successful 设置为 Truefinal_status 为 "done_no_reply"。
# _plan_and_action_loop 之后会检查这个状态。
# (这里的 final_status, final_reason, action_successful 已在上面设置)
elif is_suitable: # 适用于 direct_reply 或 send_new_message (RG决定发送且检查通过)
logger.info(f"[私聊][{self.private_name}] 动作 '{action}': 找到合适的回复,准备发送。")
# 清除上次的拒绝信息 (因为本次成功了)
conversation_info.last_reply_rejection_reason = None
conversation_info.last_rejected_reply_content = None
# --- c. 发送回复 ---
self.generated_reply = generated_content # 使用最后一次检查通过的内容
timestamp_before_sending = time.time() # 记录发送前时间戳
self.generated_reply = generated_content_for_check_or_send
timestamp_before_sending = time.time()
logger.debug(
f"[私聊][{self.private_name}] 动作 '{action}': 记录发送前时间戳: {timestamp_before_sending:.2f}"
)
self.state = ConversationState.SENDING # 更新状态为发送中
# 调用内部发送方法
send_success = await self._send_reply()
send_end_time = time.time() # 记录发送结束时间
self.state = ConversationState.SENDING
send_success = await self._send_reply() # _send_reply 内部会更新 my_message_count
send_end_time = time.time()
if send_success:
# 如果发送成功
action_successful = True # 标记动作成功
# final_status 和 final_reason 会在 finally 中设置
action_successful = True
logger.info(f"[私聊][{self.private_name}] 动作 '{action}': 成功发送回复.")
# 更新空闲计时器
if self.idle_conversation_starter:
await self.idle_conversation_starter.update_last_message_time(send_end_time)
# --- d. 清理已处理消息 ---
current_unprocessed_messages = getattr(observation_info, "unprocessed_messages", [])
message_ids_to_clear: Set[str] = set()
# 遍历所有未处理消息
for msg in current_unprocessed_messages:
msg_time = msg.get("time")
msg_id = msg.get("message_id")
sender_id = msg.get("user_info", {}).get("user_id")
# 规则:只清理【发送前】收到的、【来自他人】的消息
sender_id_info = msg.get("user_info", {})
sender_id = str(sender_id_info.get("user_id")) if sender_id_info else None
if (
msg_id
and msg_time
and sender_id != self.bot_qq_str
and msg_time < timestamp_before_sending
and sender_id != self.bot_qq_str # 确保是对方的消息
and msg_time < timestamp_before_sending # 只清理发送前的
):
message_ids_to_clear.add(msg_id)
# 如果有需要清理的消息,调用清理方法
if message_ids_to_clear:
logger.debug(
f"[私聊][{self.private_name}] 准备清理 {len(message_ids_to_clear)} 条发送前(他人)消息: {message_ids_to_clear}"
@ -1054,73 +1134,66 @@ class Conversation:
else:
logger.debug(f"[私聊][{self.private_name}] 没有需要清理的发送前(他人)消息。")
# --- e. 决定下一轮规划类型 ---
# 从 conversation_info 获取【规划期间】收到的【他人】新消息数量
other_new_msg_count_during_planning = getattr(
conversation_info, "other_new_messages_during_planning_count", 0
)
# 规则:如果规划期间收到他人新消息 (0 < count <= 2),则下一轮强制初始回复
if other_new_msg_count_during_planning > 0:
if other_new_msg_count_during_planning > 0 and action == "direct_reply":
logger.info(
f"[私聊][{self.private_name}] 因规划期间收到 {other_new_msg_count_during_planning} 条他人新消息,下一轮强制使用【初始回复】逻辑。"
)
conversation_info.last_successful_reply_action = None # 强制初始回复
conversation_info.my_message_count = 0 # 自身发言数量清零
else:
# 规则:如果规划期间【没有】收到他人新消息,则允许追问
conversation_info.last_successful_reply_action = None
# conversation_info.my_message_count = 0 # 不在这里重置,因为刚发了一条
elif action == "direct_reply" or action == "send_new_message": # 成功发送后
logger.info(
f"[私聊][{self.private_name}] 规划期间无他人新消息,下一轮【允许】使用追问逻辑 (基于 '{action}')。"
)
conversation_info.last_successful_reply_action = action # 允许追问
if conversation_info: # 确保 conversation_info 存在
conversation_info.current_instance_message_count += 1
logger.debug(f"[私聊][{self.private_name}] 实例消息计数(机器人发送后)增加到: {conversation_info.current_instance_message_count}")
if self.relationship_updater:
await self.relationship_updater.update_relationship_incremental(
conversation_info=conversation_info,
observation_info=observation_info,
chat_observer_for_history=self.chat_observer
f"[私聊][{self.private_name}] 成功执行 '{action}', 下一轮【允许】使用追问逻辑。"
)
conversation_info.last_successful_reply_action = action
sent_reply_summary = self.generated_reply[:50] if self.generated_reply else "空回复"
event_for_emotion_update = f"你刚刚发送了消息: '{sent_reply_summary}...'"
if self.emotion_updater:
await self.emotion_updater.update_emotion_based_on_context(
conversation_info=conversation_info,
observation_info=observation_info,
chat_observer_for_history=self.chat_observer,
event_description=event_for_emotion_update
)
else:
# 如果发送失败
if conversation_info:
conversation_info.current_instance_message_count += 1
logger.debug(f"[私聊][{self.private_name}] 实例消息计数(机器人发送后)增加到: {conversation_info.current_instance_message_count}")
if self.relationship_updater:
await self.relationship_updater.update_relationship_incremental(
conversation_info=conversation_info,
observation_info=observation_info,
chat_observer_for_history=self.chat_observer
)
sent_reply_summary = self.generated_reply[:50] if self.generated_reply else "空回复"
event_for_emotion_update = f"你刚刚发送了消息: '{sent_reply_summary}...'"
if self.emotion_updater:
await self.emotion_updater.update_emotion_based_on_context(
conversation_info=conversation_info,
observation_info=observation_info,
chat_observer_for_history=self.chat_observer,
event_description=event_for_emotion_update
)
else: # 发送失败
logger.error(f"[私聊][{self.private_name}] 动作 '{action}': 发送回复失败。")
final_status = "recall" # 发送失败,标记为 recall
final_status = "recall"
final_reason = "发送回复时失败"
# 重置追问状态
conversation_info.last_successful_reply_action = None
conversation_info.my_message_count = 0 # 发送失败,重置计数
elif need_replan_from_checker:
# 如果 Checker 要求重新规划
logger.warning(
f"[私聊][{self.private_name}] 动作 '{action}' 因 ReplyChecker 要求而被取消,将重新规划。原因: {check_reason}"
)
final_status = "recall" # 标记为 recall
final_status = "recall"
final_reason = f"回复检查要求重新规划: {check_reason}"
# # 重置追问状态
# conversation_info.last_successful_reply_action = None
conversation_info.last_successful_reply_action = None
# my_message_count 保持不变,因为没有成功发送
else:
# 达到最大尝试次数仍未找到合适回复
else: # 达到最大尝试次数仍未找到合适回复
logger.warning(
f"[私聊][{self.private_name}] 动作 '{action}': 达到最大尝试次数 ({max_reply_attempts}),未能生成/检查通过合适的回复。最终原因: {check_reason}"
)
final_status = "recall" # 标记为 recall
final_status = "recall"
final_reason = f"尝试{max_reply_attempts}次后失败: {check_reason}"
# # 重置追问状态
# conversation_info.last_successful_reply_action = None
conversation_info.last_successful_reply_action = None
# my_message_count 保持不变
# 2. 处理发送告别语动作 (保持简单,不加重试)
elif action == "say_goodbye":

View File

@ -64,7 +64,7 @@ PROMPT_SEND_NEW_MESSAGE = """
你正在和{sender_name}在QQ上私聊**并且刚刚你已经发送了一条或多条消息**
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
现在请根据以下信息再发一条新消息
现在请根据以下信息判断你是否要继续发一条新消息当然如果你决定继续发消息不合适也可以不发
当前对话目标{goals_str}
@ -79,7 +79,9 @@ PROMPT_SEND_NEW_MESSAGE = """
{last_rejection_info}
请根据上述信息结合聊天记录继续发一条新消息例如对之前消息的补充深入话题或追问等等该消息应该
{spam_warning_info}
请根据上述信息判断你是否要继续发一条新消息例如对之前消息的补充深入话题或追问等等如果你觉得要发送该消息应该
1. 符合对话目标""的角度发言不要自己与自己对话
2. 符合你的性格特征和身份细节
3. 通俗易懂自然流畅像正常聊天一样简短通常20字以内除非特殊情况
@ -88,10 +90,14 @@ PROMPT_SEND_NEW_MESSAGE = """
请注意把握聊天内容不用太有条理可以有个性请分清""和对方说的话不要把""说的话当做对方说的话这是你自己说的话
这条消息可以自然随意自然一些就像真人一样注意把握聊天内容整体风格可以平和简短不要刻意突出自身学科背景不要说你说过的话可以简短多简短都可以但是避免冗长
请你注意不要输出多余内容(包括前后缀冒号和引号括号表情等)只输出消息内容
不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )
如果你决定继续发消息不合适也可以不发送
请直接输出回复内容不需要任何额外格式"""
请严格按照以下JSON格式输出你的选择和消息内容不要包含任何其他说明或非JSON文本
{{
"send": "yes/no",
"txt": "如果选择发送,这里是具体的消息文本。如果选择不发送,这里也填写 'no'"
}}
"""
# Prompt for say_goodbye (告别语生成)
PROMPT_FAREWELL = """
@ -125,7 +131,7 @@ class ReplyGenerator:
self.llm = LLMRequest(
model=global_config.llm_PFC_chat,
temperature=global_config.llm_PFC_chat["temp"],
max_tokens=300,
max_tokens=300, # 对于JSON输出这个可能需要适当调整但一般回复短JSON结构也简单
request_type="reply_generation",
)
self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3)
@ -143,20 +149,18 @@ class ReplyGenerator:
Args:
observation_info: 观察信息
conversation_info: 对话信息
action_type: 当前执行的动作类型 ('direct_reply' 'send_new_message')
action_type: 当前执行的动作类型 ('direct_reply', 'send_new_message', 'say_goodbye')
Returns:
str: 生成的回复
str: 生成的回复
对于 'direct_reply' 'say_goodbye'返回纯文本回复
对于 'send_new_message'返回包含决策和文本的JSON字符串
"""
# 构建提示词
logger.debug(
f"[私聊][{self.private_name}]开始生成回复 (动作类型: {action_type}):当前目标: {conversation_info.goal_list}"
)
# --- 构建通用 Prompt 参数 ---
# (这部分逻辑基本不变)
# 构建对话目标 (goals_str)
goals_str = ""
if conversation_info.goal_list:
for goal_reason in conversation_info.goal_list:
@ -171,9 +175,8 @@ class ReplyGenerator:
reasoning = str(reasoning) if reasoning is not None else "没有明确原因"
goals_str += f"- 目标:{goal}\n 原因:{reasoning}\n"
else:
goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况
goals_str = "- 目前没有明确对话目标\n"
# 获取聊天历史记录 (chat_history_text)
chat_history_text = observation_info.chat_history_str
if observation_info.new_messages_count > 0 and observation_info.unprocessed_messages:
new_messages_list = observation_info.unprocessed_messages
@ -192,17 +195,14 @@ class ReplyGenerator:
f"\n--- 以上均为已读消息,未读消息均已处理完毕 ---\n"
)
# 获取 sender_name, relationship_text, current_emotion_text
sender_name_str = getattr(observation_info, 'sender_name', '对方')
if not sender_name_str: sender_name_str = '对方'
relationship_text_str = getattr(conversation_info, 'relationship_text', '你们还不熟悉。')
current_emotion_text_str = getattr(conversation_info, 'current_emotion_text', '心情平静。')
# 构建 Persona 文本 (persona_text)
persona_text = f"你的名字是{self.name}{self.personality_info}"
retrieval_context = chat_history_text # 使用前面构建好的 chat_history_text
# 调用共享函数进行检索
retrieval_context = chat_history_text
retrieved_memory_str, retrieved_knowledge_str = await retrieve_contextual_info(
retrieval_context, self.private_name
)
@ -210,9 +210,7 @@ class ReplyGenerator:
f"[私聊][{self.private_name}] (ReplyGenerator) 统一检索完成。记忆: {'' if '回忆起' in retrieved_memory_str else ''} / 知识: {'' if '出错' not in retrieved_knowledge_str and '无相关知识' not in retrieved_knowledge_str else ''}"
)
# --- 修改:构建上次回复失败原因和内容提示 ---
last_rejection_info_str = ""
# 检查 conversation_info 是否有上次拒绝的原因和内容,并且它们都不是 None
last_reason = getattr(conversation_info, "last_reply_rejection_reason", None)
last_content = getattr(conversation_info, "last_rejected_reply_content", None)
@ -220,7 +218,7 @@ class ReplyGenerator:
last_rejection_info_str = (
f"\n------\n"
f"【重要提示:你上一次尝试回复时失败了,以下是详细信息】\n"
f"上次试图发送的消息内容: “{last_content}\n" # <-- 显示上次内容
f"上次试图发送的消息内容: “{last_content}\n"
f"失败原因: “{last_reason}\n"
f"请根据【消息内容】和【失败原因】调整你的新回复,避免重复之前的错误。\n"
f"------\n"
@ -230,42 +228,67 @@ class ReplyGenerator:
f" 内容: {last_content}\n"
f" 原因: {last_reason}"
)
# 新增:构建刷屏警告信息 for PROMPT_SEND_NEW_MESSAGE
spam_warning_message = ""
if action_type == "send_new_message": # 只在 send_new_message 时构建刷屏警告
if conversation_info.my_message_count > 5:
spam_warning_message = f"⚠️【警告】**你已连续发送{str(conversation_info.my_message_count)}条消息!请谨慎考虑是否继续发送!以免刷屏对造成对方困扰!**"
elif conversation_info.my_message_count > 2:
spam_warning_message = f"💬【提示】**你已连续发送{str(conversation_info.my_message_count)}条消息。如果非必要,请避免连续发送,以免给对方造成困扰。**"
if spam_warning_message:
spam_warning_message = f"\n{spam_warning_message}\n"
# --- 选择 Prompt ---
if action_type == "send_new_message":
prompt_template = PROMPT_SEND_NEW_MESSAGE
logger.info(f"[私聊][{self.private_name}]使用 PROMPT_SEND_NEW_MESSAGE (追问生成)")
elif action_type == "say_goodbye": # 处理告别动作
logger.info(f"[私聊][{self.private_name}]使用 PROMPT_SEND_NEW_MESSAGE (追问/补充生成, 期望JSON输出)")
elif action_type == "say_goodbye":
prompt_template = PROMPT_FAREWELL
logger.info(f"[私聊][{self.private_name}]使用 PROMPT_FAREWELL (告别语生成)")
else: # 默认使用 direct_reply 的 prompt (包括 'direct_reply' 或其他未明确处理的类型)
else:
prompt_template = PROMPT_DIRECT_REPLY
logger.info(f"[私聊][{self.private_name}]使用 PROMPT_DIRECT_REPLY (首次/非连续回复生成)")
# --- 格式化最终的 Prompt ---
try: # <--- 增加 try-except 块处理可能的 format 错误
current_time_value = "获取时间失败" # 默认值
try:
current_time_value = "获取时间失败"
if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str:
current_time_value = observation_info.current_time_str
if action_type == "say_goodbye":
prompt = prompt_template.format(
persona_text=persona_text,
chat_history_text=chat_history_text,
current_time_str=current_time_value, # 添加时间
current_time_str=current_time_value,
sender_name=sender_name_str,
relationship_text=relationship_text_str,
current_emotion_text=current_emotion_text_str
)
else:
elif action_type == "send_new_message": # PROMPT_SEND_NEW_MESSAGE 增加了 spam_warning_info
prompt = prompt_template.format(
persona_text=persona_text,
goals_str=goals_str,
chat_history_text=chat_history_text,
retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。",
retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。",
last_rejection_info=last_rejection_info_str, # <--- 新增传递上次拒绝原因
current_time_str=current_time_value, # 新增:传入当前时间字符串
last_rejection_info=last_rejection_info_str,
current_time_str=current_time_value,
sender_name=sender_name_str,
relationship_text=relationship_text_str,
current_emotion_text=current_emotion_text_str,
spam_warning_info=spam_warning_message # 添加 spam_warning_info
)
else: # PROMPT_DIRECT_REPLY (没有 spam_warning_info)
prompt = prompt_template.format(
persona_text=persona_text,
goals_str=goals_str,
chat_history_text=chat_history_text,
retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。",
retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。",
last_rejection_info=last_rejection_info_str,
current_time_str=current_time_value,
sender_name=sender_name_str,
relationship_text=relationship_text_str,
current_emotion_text=current_emotion_text_str
@ -274,8 +297,7 @@ class ReplyGenerator:
logger.error(
f"[私聊][{self.private_name}]格式化 Prompt 时出错,缺少键: {e}。请检查 Prompt 模板和传递的参数。"
)
# 返回错误信息或默认回复
return "抱歉,准备回复时出了点问题,请检查一下我的代码..."
return "抱歉,准备回复时出了点问题,请检查一下我的代码..." # 对于JSON期望的场景这里可能也需要返回一个固定的错误JSON
except Exception as fmt_err:
logger.error(f"[私聊][{self.private_name}]格式化 Prompt 时发生未知错误: {fmt_err}")
return "抱歉,准备回复时出了点内部错误,请检查一下我的代码..."
@ -284,19 +306,30 @@ class ReplyGenerator:
logger.debug(f"[私聊][{self.private_name}]发送到LLM的生成提示词:\n------\n{prompt}\n------")
try:
content, _ = await self.llm.generate_response_async(prompt)
logger.debug(f"[私聊][{self.private_name}]生成的回复: {content}")
# 移除旧的检查新消息逻辑,这应该由 conversation 控制流处理
# 对于 PROMPT_SEND_NEW_MESSAGE我们期望 content 是一个 JSON 字符串
# 对于其他 promptscontent 是纯文本回复
# 该方法现在直接返回 LLM 的原始输出,由调用者 (conversation._handle_action) 负责解析
logger.debug(f"[私聊][{self.private_name}]LLM原始生成内容: {content}")
return content
except Exception as e:
logger.error(f"[私聊][{self.private_name}]生成回复时出错: {e}")
return "抱歉,我现在有点混乱,让我重新思考一下..."
# 根据 action_type 返回不同的错误指示
if action_type == "send_new_message":
# 返回一个表示错误的JSON让调用方知道出错了但仍能解析
return """{{
"send": "no",
"txt": "LLM生成回复时出错"
}}""".strip()
else:
return "抱歉,我现在有点混乱,让我重新思考一下..."
# check_reply 方法保持不变
async def check_reply(
self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_str: str, retry_count: int = 0
self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_str: str, current_time_str: str, retry_count: int = 0
) -> Tuple[bool, str, bool]:
"""检查回复是否合适
(此方法逻辑保持不变)
(此方法逻辑保持不变, 但注意 current_time_str 参数的传递)
"""
return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count)
# 确保 current_time_str 被正确传递给 reply_checker.check
return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, current_time_str, retry_count)