src/plugins/PFC/action_planner.py
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Tuple
|
from typing import Tuple, Optional # 增加了 Optional
|
||||||
from src.common.logger import get_module_logger, LogConfig, PFC_ACTION_PLANNER_STYLE_CONFIG
|
from src.common.logger import get_module_logger, LogConfig, PFC_ACTION_PLANNER_STYLE_CONFIG
|
||||||
from ..models.utils_model import LLMRequest
|
from ..models.utils_model import LLMRequest
|
||||||
from ...config.config import global_config
|
from ...config.config import global_config
|
||||||
@@ -18,14 +18,73 @@ pfc_action_log_config = LogConfig(
|
|||||||
logger = get_module_logger("action_planner", config=pfc_action_log_config)
|
logger = get_module_logger("action_planner", config=pfc_action_log_config)
|
||||||
|
|
||||||
|
|
||||||
# 注意:这个 ActionPlannerInfo 类似乎没有在 ActionPlanner 中使用,
|
# --- 定义 Prompt 模板 ---
|
||||||
# 如果确实没用,可以考虑移除,但暂时保留以防万一。
|
|
||||||
class ActionPlannerInfo:
|
# Prompt(1): 首次回复或非连续回复时的决策 Prompt
|
||||||
def __init__(self):
|
PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以发言,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方:
|
||||||
self.done_action = []
|
|
||||||
self.goal_list = []
|
【当前对话目标】
|
||||||
self.knowledge_list = []
|
{goals_str}
|
||||||
self.memory_list = []
|
|
||||||
|
【最近行动历史概要】
|
||||||
|
{action_history_summary}
|
||||||
|
【上一次行动的详细情况和结果】
|
||||||
|
{last_action_context}
|
||||||
|
【时间和超时提示】
|
||||||
|
{time_since_last_bot_message_info}{timeout_context}
|
||||||
|
【最近的对话记录】(包括你已成功发送的消息 和 新收到的消息)
|
||||||
|
{chat_history_text}
|
||||||
|
|
||||||
|
------
|
||||||
|
可选行动类型以及解释:
|
||||||
|
fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择
|
||||||
|
wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是较安全的选择)
|
||||||
|
listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择
|
||||||
|
direct_reply: 直接回复对方
|
||||||
|
rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择
|
||||||
|
end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择
|
||||||
|
block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择
|
||||||
|
|
||||||
|
请以JSON格式输出你的决策:
|
||||||
|
{{
|
||||||
|
"action": "选择的行动类型 (必须是上面列表中的一个)",
|
||||||
|
"reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的)"
|
||||||
|
}}
|
||||||
|
|
||||||
|
注意:请严格按照JSON格式输出,不要包含任何其他内容。"""
|
||||||
|
|
||||||
|
# Prompt(2): 上一次成功回复后,决定继续发言时的决策 Prompt
|
||||||
|
PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊,刚刚你已经回复了对方,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以继续发送新消息,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方:
|
||||||
|
|
||||||
|
【当前对话目标】
|
||||||
|
{goals_str}
|
||||||
|
|
||||||
|
【最近行动历史概要】
|
||||||
|
{action_history_summary}
|
||||||
|
【上一次行动的详细情况和结果】
|
||||||
|
{last_action_context}
|
||||||
|
【时间和超时提示】
|
||||||
|
{time_since_last_bot_message_info}{timeout_context}
|
||||||
|
【最近的对话记录】(包括你已成功发送的消息 和 新收到的消息)
|
||||||
|
{chat_history_text}
|
||||||
|
|
||||||
|
------
|
||||||
|
可选行动类型以及解释:
|
||||||
|
fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择
|
||||||
|
wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是不错的选择)
|
||||||
|
listening: 倾听对方发言(虽然你刚发过言,但如果对方立刻回复且明显话没说完,可以选择这个)
|
||||||
|
send_new_message: 发送一条新消息继续对话,允许适当的追问、补充、深入话题,或开启相关新话题。**但是避免在因重复被拒后立即使用,也不要在对方没有回复的情况下过多的“消息轰炸”或重复发言**
|
||||||
|
rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择
|
||||||
|
end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择
|
||||||
|
block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择
|
||||||
|
|
||||||
|
请以JSON格式输出你的决策:
|
||||||
|
{{
|
||||||
|
"action": "选择的行动类型 (必须是上面列表中的一个)",
|
||||||
|
"reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的。请说明你为什么选择继续发言而不是等待,以及打算发送什么类型的新消息连续发言,必须记录已经发言了几次)"
|
||||||
|
}}
|
||||||
|
|
||||||
|
注意:请严格按照JSON格式输出,不要包含任何其他内容。"""
|
||||||
|
|
||||||
|
|
||||||
# ActionPlanner 类定义,顶格
|
# ActionPlanner 类定义,顶格
|
||||||
@@ -43,18 +102,22 @@ class ActionPlanner:
|
|||||||
self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2)
|
self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2)
|
||||||
self.name = global_config.BOT_NICKNAME
|
self.name = global_config.BOT_NICKNAME
|
||||||
self.chat_observer = ChatObserver.get_instance(stream_id)
|
self.chat_observer = ChatObserver.get_instance(stream_id)
|
||||||
|
# self.action_planner_info = ActionPlannerInfo() # 移除未使用的变量
|
||||||
|
|
||||||
async def plan(self, observation_info: ObservationInfo, conversation_info: ConversationInfo) -> Tuple[str, str]:
|
# 修改 plan 方法签名,增加 last_successful_reply_action 参数
|
||||||
|
async def plan(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, last_successful_reply_action: Optional[str]) -> Tuple[str, str]:
|
||||||
"""规划下一步行动
|
"""规划下一步行动
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
observation_info: 决策信息
|
observation_info: 决策信息
|
||||||
conversation_info: 对话信息
|
conversation_info: 对话信息
|
||||||
|
last_successful_reply_action: 上一次成功的回复动作类型 ('direct_reply' 或 'send_new_message' 或 None)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[str, str]: (行动类型, 行动原因)
|
Tuple[str, str]: (行动类型, 行动原因)
|
||||||
"""
|
"""
|
||||||
# --- 获取 Bot 上次发言时间信息 ---
|
# --- 获取 Bot 上次发言时间信息 ---
|
||||||
|
# (这部分逻辑不变)
|
||||||
time_since_last_bot_message_info = ""
|
time_since_last_bot_message_info = ""
|
||||||
try:
|
try:
|
||||||
bot_id = str(global_config.BOT_QQ)
|
bot_id = str(global_config.BOT_QQ)
|
||||||
@@ -79,10 +142,12 @@ class ActionPlanner:
|
|||||||
logger.warning("ObservationInfo object might not have chat_history attribute yet for bot time check.")
|
logger.warning("ObservationInfo object might not have chat_history attribute yet for bot time check.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"获取 Bot 上次发言时间时出错: {e}")
|
logger.warning(f"获取 Bot 上次发言时间时出错: {e}")
|
||||||
# --- 获取 Bot 上次发言时间信息结束 ---
|
|
||||||
|
|
||||||
|
|
||||||
|
# --- 获取超时提示信息 ---
|
||||||
|
# (这部分逻辑不变)
|
||||||
timeout_context = ""
|
timeout_context = ""
|
||||||
try: # 添加 try-except 以增加健壮性
|
try:
|
||||||
if hasattr(conversation_info, "goal_list") and conversation_info.goal_list:
|
if hasattr(conversation_info, "goal_list") and conversation_info.goal_list:
|
||||||
last_goal_tuple = conversation_info.goal_list[-1]
|
last_goal_tuple = conversation_info.goal_list[-1]
|
||||||
if isinstance(last_goal_tuple, tuple) and len(last_goal_tuple) > 0:
|
if isinstance(last_goal_tuple, tuple) and len(last_goal_tuple) > 0:
|
||||||
@@ -100,12 +165,12 @@ class ActionPlanner:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"检查超时目标时出错: {e}")
|
logger.warning(f"检查超时目标时出错: {e}")
|
||||||
|
|
||||||
# 构建提示词
|
# --- 构建通用 Prompt 参数 ---
|
||||||
logger.debug(f"开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") # 使用 getattr
|
logger.debug(f"开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}")
|
||||||
|
|
||||||
# 构建对话目标 (goals_str)
|
# 构建对话目标 (goals_str)
|
||||||
goals_str = ""
|
goals_str = ""
|
||||||
try: # 添加 try-except
|
try:
|
||||||
if hasattr(conversation_info, "goal_list") and conversation_info.goal_list:
|
if hasattr(conversation_info, "goal_list") and conversation_info.goal_list:
|
||||||
for goal_reason in conversation_info.goal_list:
|
for goal_reason in conversation_info.goal_list:
|
||||||
if isinstance(goal_reason, tuple) and len(goal_reason) > 0:
|
if isinstance(goal_reason, tuple) and len(goal_reason) > 0:
|
||||||
@@ -120,7 +185,7 @@ class ActionPlanner:
|
|||||||
goal = str(goal) if goal is not None else "目标内容缺失"
|
goal = str(goal) if goal is not None else "目标内容缺失"
|
||||||
reasoning = str(reasoning) if reasoning is not None else "没有明确原因"
|
reasoning = str(reasoning) if reasoning is not None else "没有明确原因"
|
||||||
goals_str += f"- 目标:{goal}\n 原因:{reasoning}\n"
|
goals_str += f"- 目标:{goal}\n 原因:{reasoning}\n"
|
||||||
if not goals_str: # 如果循环后 goals_str 仍为空
|
if not goals_str:
|
||||||
goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n"
|
goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n"
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning("ConversationInfo object might not have goal_list attribute yet.")
|
logger.warning("ConversationInfo object might not have goal_list attribute yet.")
|
||||||
@@ -134,7 +199,7 @@ class ActionPlanner:
|
|||||||
try:
|
try:
|
||||||
if hasattr(observation_info, "chat_history") and observation_info.chat_history:
|
if hasattr(observation_info, "chat_history") and observation_info.chat_history:
|
||||||
chat_history_text = observation_info.chat_history_str
|
chat_history_text = observation_info.chat_history_str
|
||||||
if not chat_history_text: # 如果历史记录是空列表
|
if not chat_history_text:
|
||||||
chat_history_text = "还没有聊天记录。\n"
|
chat_history_text = "还没有聊天记录。\n"
|
||||||
else:
|
else:
|
||||||
chat_history_text = "还没有聊天记录。\n"
|
chat_history_text = "还没有聊天记录。\n"
|
||||||
@@ -152,9 +217,6 @@ class ActionPlanner:
|
|||||||
chat_history_text += (
|
chat_history_text += (
|
||||||
f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}"
|
f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}"
|
||||||
)
|
)
|
||||||
# 清理消息应该由调用者或 observation_info 内部逻辑处理,这里不再调用 clear
|
|
||||||
# if hasattr(observation_info, 'clear_unprocessed_messages'):
|
|
||||||
# observation_info.clear_unprocessed_messages()
|
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"ObservationInfo has new_messages_count > 0 but unprocessed_messages is empty or missing."
|
"ObservationInfo has new_messages_count > 0 but unprocessed_messages is empty or missing."
|
||||||
@@ -167,11 +229,11 @@ class ActionPlanner:
|
|||||||
chat_history_text = "处理聊天记录时出错。\n"
|
chat_history_text = "处理聊天记录时出错。\n"
|
||||||
|
|
||||||
# 构建 Persona 文本 (persona_text)
|
# 构建 Persona 文本 (persona_text)
|
||||||
|
# (这部分逻辑不变)
|
||||||
identity_details_only = self.identity_detail_info
|
identity_details_only = self.identity_detail_info
|
||||||
identity_addon = ""
|
identity_addon = ""
|
||||||
if isinstance(identity_details_only, str):
|
if isinstance(identity_details_only, str):
|
||||||
pronouns = ["你", "我", "他"]
|
pronouns = ["你", "我", "他"]
|
||||||
# original_details = identity_details_only
|
|
||||||
for p in pronouns:
|
for p in pronouns:
|
||||||
if identity_details_only.startswith(p):
|
if identity_details_only.startswith(p):
|
||||||
identity_details_only = identity_details_only[len(p) :]
|
identity_details_only = identity_details_only[len(p) :]
|
||||||
@@ -183,12 +245,13 @@ class ActionPlanner:
|
|||||||
identity_addon = f"并且{cleaned_details}"
|
identity_addon = f"并且{cleaned_details}"
|
||||||
persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。"
|
persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。"
|
||||||
|
|
||||||
# --- 构建更清晰的行动历史和上一次行动结果 ---
|
|
||||||
|
# 构建行动历史和上一次行动结果 (action_history_summary, last_action_context)
|
||||||
|
# (这部分逻辑不变)
|
||||||
action_history_summary = "你最近执行的行动历史:\n"
|
action_history_summary = "你最近执行的行动历史:\n"
|
||||||
last_action_context = "关于你【上一次尝试】的行动:\n"
|
last_action_context = "关于你【上一次尝试】的行动:\n"
|
||||||
|
|
||||||
action_history_list = []
|
action_history_list = []
|
||||||
try: # 添加 try-except
|
try:
|
||||||
if hasattr(conversation_info, "done_action") and conversation_info.done_action:
|
if hasattr(conversation_info, "done_action") and conversation_info.done_action:
|
||||||
action_history_list = conversation_info.done_action[-5:]
|
action_history_list = conversation_info.done_action[-5:]
|
||||||
else:
|
else:
|
||||||
@@ -216,14 +279,12 @@ class ActionPlanner:
|
|||||||
final_reason = action_data.get("final_reason", "")
|
final_reason = action_data.get("final_reason", "")
|
||||||
action_time = action_data.get("time", "")
|
action_time = action_data.get("time", "")
|
||||||
elif isinstance(action_data, tuple):
|
elif isinstance(action_data, tuple):
|
||||||
if len(action_data) > 0:
|
# 假设旧格式兼容
|
||||||
action_type = action_data[0]
|
if len(action_data) > 0: action_type = action_data[0]
|
||||||
if len(action_data) > 1:
|
if len(action_data) > 1: plan_reason = action_data[1] # 可能是规划原因或最终原因
|
||||||
plan_reason = action_data[1]
|
if len(action_data) > 2: status = action_data[2]
|
||||||
if len(action_data) > 2:
|
if status == "recall" and len(action_data) > 3: final_reason = action_data[3]
|
||||||
status = action_data[2]
|
elif status == "done" and action_type in ["direct_reply", "send_new_message"]: plan_reason = "成功发送" # 简化显示
|
||||||
if status == "recall" and len(action_data) > 3:
|
|
||||||
final_reason = action_data[3]
|
|
||||||
|
|
||||||
reason_text = f", 失败/取消原因: {final_reason}" if final_reason else ""
|
reason_text = f", 失败/取消原因: {final_reason}" if final_reason else ""
|
||||||
summary_line = f"- 时间:{action_time}, 尝试行动:'{action_type}', 状态:{status}{reason_text}"
|
summary_line = f"- 时间:{action_time}, 尝试行动:'{action_type}', 状态:{status}{reason_text}"
|
||||||
@@ -234,50 +295,39 @@ class ActionPlanner:
|
|||||||
last_action_context += f"- 当时规划的【原因】是: {plan_reason}\n"
|
last_action_context += f"- 当时规划的【原因】是: {plan_reason}\n"
|
||||||
if status == "done":
|
if status == "done":
|
||||||
last_action_context += "- 该行动已【成功执行】。\n"
|
last_action_context += "- 该行动已【成功执行】。\n"
|
||||||
|
# 记录这次成功的行动类型,供下次决策
|
||||||
|
# self.last_successful_action_type = action_type # 不在这里记录,由 conversation 控制
|
||||||
elif status == "recall":
|
elif status == "recall":
|
||||||
last_action_context += "- 但该行动最终【未能执行/被取消】。\n"
|
last_action_context += "- 但该行动最终【未能执行/被取消】。\n"
|
||||||
if final_reason:
|
if final_reason:
|
||||||
last_action_context += f"- 【重要】失败/取消的具体原因是: “{final_reason}”\n"
|
last_action_context += f"- 【重要】失败/取消的具体原因是: “{final_reason}”\n"
|
||||||
else:
|
else:
|
||||||
last_action_context += "- 【重要】失败/取消原因未明确记录。\n"
|
last_action_context += "- 【重要】失败/取消原因未明确记录。\n"
|
||||||
|
# self.last_successful_action_type = None # 行动失败,清除记录
|
||||||
else:
|
else:
|
||||||
last_action_context += f"- 该行动当前状态: {status}\n"
|
last_action_context += f"- 该行动当前状态: {status}\n"
|
||||||
|
# self.last_successful_action_type = None # 非完成状态,清除记录
|
||||||
|
|
||||||
# --- 构建最终的 Prompt ---
|
# --- 选择 Prompt ---
|
||||||
prompt = f"""{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以发言,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方:
|
if last_successful_reply_action in ['direct_reply', 'send_new_message']:
|
||||||
|
prompt_template = PROMPT_FOLLOW_UP
|
||||||
|
logger.info("使用 PROMPT_FOLLOW_UP (追问决策)")
|
||||||
|
else:
|
||||||
|
prompt_template = PROMPT_INITIAL_REPLY
|
||||||
|
logger.info("使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)")
|
||||||
|
|
||||||
【当前对话目标】
|
# --- 格式化最终的 Prompt ---
|
||||||
{goals_str if goals_str.strip() else "- 目前没有明确对话目标,请考虑设定一个。"}
|
prompt = prompt_template.format(
|
||||||
|
persona_text=persona_text,
|
||||||
|
goals_str=goals_str if goals_str.strip() else "- 目前没有明确对话目标,请考虑设定一个。",
|
||||||
|
action_history_summary=action_history_summary,
|
||||||
|
last_action_context=last_action_context,
|
||||||
|
time_since_last_bot_message_info=time_since_last_bot_message_info,
|
||||||
|
timeout_context=timeout_context,
|
||||||
|
chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"发送到LLM的最终提示词:\n------\n{prompt}\n------")
|
||||||
【最近行动历史概要】
|
|
||||||
{action_history_summary}
|
|
||||||
【上一次行动的详细情况和结果】
|
|
||||||
{last_action_context}
|
|
||||||
【时间和超时提示】
|
|
||||||
{time_since_last_bot_message_info}{timeout_context}
|
|
||||||
【最近的对话记录】(包括你已成功发送的消息 和 新收到的消息)
|
|
||||||
{chat_history_text if chat_history_text.strip() else "还没有聊天记录。"}
|
|
||||||
|
|
||||||
------
|
|
||||||
可选行动类型以及解释:
|
|
||||||
fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择
|
|
||||||
wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是较安全的选择)
|
|
||||||
listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择
|
|
||||||
direct_reply: 直接回复或发送新消息,允许适当的追问和深入话题,**但是避免在因重复被拒后立即使用,也不要在对方没有回复的情况下过多的“消息轰炸”或重复发言**
|
|
||||||
rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择
|
|
||||||
end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择
|
|
||||||
block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择
|
|
||||||
|
|
||||||
请以JSON格式输出你的决策:
|
|
||||||
{{
|
|
||||||
"action": "选择的行动类型 (必须是上面列表中的一个)",
|
|
||||||
"reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的,如果你连续发言,必须记录已经发言了几次)"
|
|
||||||
}}
|
|
||||||
|
|
||||||
注意:请严格按照JSON格式输出,不要包含任何其他内容。"""
|
|
||||||
|
|
||||||
logger.debug(f"发送到LLM的提示词 (已更新): {prompt}")
|
|
||||||
try:
|
try:
|
||||||
content, _ = await self.llm.generate_response_async(prompt)
|
content, _ = await self.llm.generate_response_async(prompt)
|
||||||
logger.debug(f"LLM原始返回内容: {content}")
|
logger.debug(f"LLM原始返回内容: {content}")
|
||||||
@@ -293,7 +343,17 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
|
|||||||
reason = result.get("reason", "LLM未提供原因,默认等待")
|
reason = result.get("reason", "LLM未提供原因,默认等待")
|
||||||
|
|
||||||
# 验证action类型
|
# 验证action类型
|
||||||
valid_actions = ["direct_reply", "fetch_knowledge", "wait", "listening", "rethink_goal", "end_conversation", "block_and_ignore"]
|
# 更新 valid_actions 列表以包含 send_new_message
|
||||||
|
valid_actions = [
|
||||||
|
"direct_reply",
|
||||||
|
"send_new_message", # 添加新动作
|
||||||
|
"fetch_knowledge",
|
||||||
|
"wait",
|
||||||
|
"listening",
|
||||||
|
"rethink_goal",
|
||||||
|
"end_conversation",
|
||||||
|
"block_and_ignore"
|
||||||
|
]
|
||||||
if action not in valid_actions:
|
if action not in valid_actions:
|
||||||
logger.warning(f"LLM返回了未知的行动类型: '{action}',强制改为 wait")
|
logger.warning(f"LLM返回了未知的行动类型: '{action}',强制改为 wait")
|
||||||
reason = f"(原始行动'{action}'无效,已强制改为wait) {reason}"
|
reason = f"(原始行动'{action}'无效,已强制改为wait) {reason}"
|
||||||
@@ -305,4 +365,4 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"规划行动时调用 LLM 或处理结果出错: {str(e)}")
|
logger.error(f"规划行动时调用 LLM 或处理结果出错: {str(e)}")
|
||||||
return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}"
|
return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}"
|
||||||
@@ -11,7 +11,7 @@ from .pfc import ChatObserver, GoalAnalyzer, DirectMessageSender
|
|||||||
from src.common.logger import get_module_logger
|
from src.common.logger import get_module_logger
|
||||||
from .action_planner import ActionPlanner
|
from .action_planner import ActionPlanner
|
||||||
from .observation_info import ObservationInfo
|
from .observation_info import ObservationInfo
|
||||||
from .conversation_info import ConversationInfo
|
from .conversation_info import ConversationInfo # 确保导入 ConversationInfo
|
||||||
from .reply_generator import ReplyGenerator
|
from .reply_generator import ReplyGenerator
|
||||||
from ..chat.chat_stream import ChatStream
|
from ..chat.chat_stream import ChatStream
|
||||||
from maim_message import UserInfo
|
from maim_message import UserInfo
|
||||||
@@ -102,9 +102,6 @@ class Conversation:
|
|||||||
self.observation_info.last_message_sender = last_user_info.user_id
|
self.observation_info.last_message_sender = last_user_info.user_id
|
||||||
self.observation_info.last_message_content = last_msg.get("processed_plain_text", "")
|
self.observation_info.last_message_content = last_msg.get("processed_plain_text", "")
|
||||||
|
|
||||||
# (可选)可以遍历 initial_messages 来设置 last_bot_speak_time 和 last_user_speak_time
|
|
||||||
# 这里为了简化,只用了最后一条消息的时间,如果需要精确的发言者时间需要遍历
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"成功加载 {len(initial_messages)} 条初始聊天记录。最后一条消息时间: {self.observation_info.last_message_time}"
|
f"成功加载 {len(initial_messages)} 条初始聊天记录。最后一条消息时间: {self.observation_info.last_message_time}"
|
||||||
)
|
)
|
||||||
@@ -134,18 +131,15 @@ class Conversation:
|
|||||||
async def _plan_and_action_loop(self):
|
async def _plan_and_action_loop(self):
|
||||||
"""思考步,PFC核心循环模块"""
|
"""思考步,PFC核心循环模块"""
|
||||||
while self.should_continue:
|
while self.should_continue:
|
||||||
|
# 忽略逻辑
|
||||||
if self.ignore_until_timestamp and time.time() < self.ignore_until_timestamp:
|
if self.ignore_until_timestamp and time.time() < self.ignore_until_timestamp:
|
||||||
# 仍在忽略期间,等待下次检查
|
await asyncio.sleep(30)
|
||||||
await asyncio.sleep(30) # 每 30 秒检查一次
|
continue
|
||||||
continue # 跳过本轮循环的剩余部分
|
|
||||||
elif self.ignore_until_timestamp and time.time() >= self.ignore_until_timestamp:
|
elif self.ignore_until_timestamp and time.time() >= self.ignore_until_timestamp:
|
||||||
# 忽略期结束,现在正常地结束对话
|
|
||||||
logger.info(f"忽略时间已到 {self.stream_id},准备结束对话。")
|
logger.info(f"忽略时间已到 {self.stream_id},准备结束对话。")
|
||||||
self.ignore_until_timestamp = None # 清除时间戳
|
self.ignore_until_timestamp = None
|
||||||
self.should_continue = False # 现在停止循环
|
self.should_continue = False
|
||||||
# (可选)在这里记录一个 'end_conversation' 动作
|
continue
|
||||||
# 或者确保管理器会基于 should_continue 为 False 来清理它
|
|
||||||
continue # 跳过本轮循环的剩余部分,让它终止
|
|
||||||
try:
|
try:
|
||||||
# --- 在规划前记录当前新消息数量 ---
|
# --- 在规划前记录当前新消息数量 ---
|
||||||
initial_new_message_count = 0
|
initial_new_message_count = 0
|
||||||
@@ -154,10 +148,13 @@ class Conversation:
|
|||||||
else:
|
else:
|
||||||
logger.warning("ObservationInfo missing 'new_messages_count' before planning.")
|
logger.warning("ObservationInfo missing 'new_messages_count' before planning.")
|
||||||
|
|
||||||
# 使用决策信息来辅助行动规划
|
# --- 调用 Action Planner ---
|
||||||
|
# 传递 self.conversation_info.last_successful_reply_action
|
||||||
action, reason = await self.action_planner.plan(
|
action, reason = await self.action_planner.plan(
|
||||||
self.observation_info, self.conversation_info
|
self.observation_info,
|
||||||
) # 注意:plan 函数内部现在不应再调用 clear_unprocessed_messages
|
self.conversation_info,
|
||||||
|
self.conversation_info.last_successful_reply_action
|
||||||
|
)
|
||||||
|
|
||||||
# --- 规划后检查是否有 *更多* 新消息到达 ---
|
# --- 规划后检查是否有 *更多* 新消息到达 ---
|
||||||
current_new_message_count = 0
|
current_new_message_count = 0
|
||||||
@@ -167,84 +164,101 @@ class Conversation:
|
|||||||
logger.warning("ObservationInfo missing 'new_messages_count' after planning.")
|
logger.warning("ObservationInfo missing 'new_messages_count' after planning.")
|
||||||
|
|
||||||
if current_new_message_count > initial_new_message_count:
|
if current_new_message_count > initial_new_message_count:
|
||||||
# 只有当规划期间消息数量 *增加* 了,才认为需要重新规划
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划"
|
f"规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划"
|
||||||
)
|
)
|
||||||
await asyncio.sleep(0.1) # 短暂延时
|
# 如果规划期间有新消息,也应该重置上次回复状态,因为现在要响应新消息了
|
||||||
continue # 跳过本次行动,重新规划
|
self.conversation_info.last_successful_reply_action = None
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
# --- 如果没有在规划期间收到更多新消息,则准备执行行动 ---
|
# 包含 send_new_message
|
||||||
|
if initial_new_message_count > 0 and action in ["direct_reply", "send_new_message"]:
|
||||||
# --- 清理未处理消息:移到这里,在执行动作前 ---
|
|
||||||
# 只有当确实有新消息被 planner 看到,并且 action 是要处理它们的时候才清理
|
|
||||||
if initial_new_message_count > 0 and action == "direct_reply":
|
|
||||||
if hasattr(self.observation_info, "clear_unprocessed_messages"):
|
if hasattr(self.observation_info, "clear_unprocessed_messages"):
|
||||||
# 确保 clear_unprocessed_messages 方法存在
|
logger.debug(f"准备执行 {action},清理 {initial_new_message_count} 条规划时已知的新消息。")
|
||||||
logger.debug(f"准备执行 direct_reply,清理 {initial_new_message_count} 条规划时已知的新消息。")
|
|
||||||
await self.observation_info.clear_unprocessed_messages()
|
await self.observation_info.clear_unprocessed_messages()
|
||||||
# 手动重置计数器,确保状态一致性(理想情况下 clear 方法会做这个)
|
|
||||||
if hasattr(self.observation_info, "new_messages_count"):
|
if hasattr(self.observation_info, "new_messages_count"):
|
||||||
self.observation_info.new_messages_count = 0
|
self.observation_info.new_messages_count = 0
|
||||||
else:
|
else:
|
||||||
logger.error("无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!")
|
logger.error("无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!")
|
||||||
# 这里可能需要考虑是否继续执行 action,或者抛出错误
|
|
||||||
|
|
||||||
# --- 执行行动 ---
|
|
||||||
await self._handle_action(action, reason, self.observation_info, self.conversation_info)
|
await self._handle_action(action, reason, self.observation_info, self.conversation_info)
|
||||||
|
|
||||||
|
# 检查是否需要结束对话 (逻辑不变)
|
||||||
goal_ended = False
|
goal_ended = False
|
||||||
if hasattr(self.conversation_info, "goal_list") and self.conversation_info.goal_list:
|
if hasattr(self.conversation_info, "goal_list") and self.conversation_info.goal_list:
|
||||||
for goal in self.conversation_info.goal_list:
|
for goal_item in self.conversation_info.goal_list:
|
||||||
if isinstance(goal, tuple) and len(goal) > 0 and goal[0] == "结束对话":
|
current_goal = None
|
||||||
goal_ended = True
|
if isinstance(goal_item, tuple) and len(goal_item) > 0:
|
||||||
break
|
current_goal = goal_item[0]
|
||||||
elif isinstance(goal, dict) and goal.get("goal") == "结束对话":
|
elif isinstance(goal_item, dict):
|
||||||
|
current_goal = goal_item.get("goal")
|
||||||
|
|
||||||
|
if current_goal == "结束对话":
|
||||||
goal_ended = True
|
goal_ended = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if goal_ended:
|
if goal_ended:
|
||||||
self.should_continue = False
|
self.should_continue = False
|
||||||
logger.info("检测到'结束对话'目标,停止循环。")
|
logger.info("检测到'结束对话'目标,停止循环。")
|
||||||
# break # 可以选择在这里直接跳出循环
|
|
||||||
|
|
||||||
except Exception as loop_err:
|
except Exception as loop_err:
|
||||||
logger.error(f"PFC主循环出错: {loop_err}")
|
logger.error(f"PFC主循环出错: {loop_err}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
# 发生严重错误时可以考虑停止,或者至少等待一下再继续
|
await asyncio.sleep(1)
|
||||||
await asyncio.sleep(1) # 发生错误时等待1秒
|
|
||||||
# 添加短暂的异步睡眠
|
|
||||||
if self.should_continue: # 只有在还需要继续循环时才 sleep
|
|
||||||
await asyncio.sleep(0.1) # 等待 0.1 秒,给其他任务执行时间
|
|
||||||
|
|
||||||
logger.info(f"PFC 循环结束 for stream_id: {self.stream_id}") # 添加日志表明循环正常结束
|
if self.should_continue:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
logger.info(f"PFC 循环结束 for stream_id: {self.stream_id}")
|
||||||
|
|
||||||
def _check_new_messages_after_planning(self):
|
def _check_new_messages_after_planning(self):
|
||||||
"""检查在规划后是否有新消息"""
|
"""检查在规划后是否有新消息"""
|
||||||
|
# 检查 ObservationInfo 是否已初始化并且有 new_messages_count 属性
|
||||||
|
if not hasattr(self, 'observation_info') or not hasattr(self.observation_info, 'new_messages_count'):
|
||||||
|
logger.warning("ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。")
|
||||||
|
return False # 或者根据需要抛出错误
|
||||||
|
|
||||||
if self.observation_info.new_messages_count > 0:
|
if self.observation_info.new_messages_count > 0:
|
||||||
logger.info(f"发现{self.observation_info.new_messages_count}条新消息,可能需要重新考虑行动")
|
logger.info(f"生成/执行动作期间收到 {self.observation_info.new_messages_count} 条新消息,取消当前动作并重新规划")
|
||||||
# 如果需要,可以在这里添加逻辑来根据新消息重新决定行动
|
# 如果有新消息,也应该重置上次回复状态
|
||||||
|
if hasattr(self, 'conversation_info'): # 确保 conversation_info 已初始化
|
||||||
|
self.conversation_info.last_successful_reply_action = None
|
||||||
|
else:
|
||||||
|
logger.warning("ConversationInfo 未初始化,无法重置 last_successful_reply_action。")
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _convert_to_message(self, msg_dict: Dict[str, Any]) -> Message:
|
def _convert_to_message(self, msg_dict: Dict[str, Any]) -> Message:
|
||||||
"""将消息字典转换为Message对象"""
|
"""将消息字典转换为Message对象"""
|
||||||
try:
|
try:
|
||||||
chat_info = msg_dict.get("chat_info", {})
|
# 尝试从 msg_dict 直接获取 chat_stream,如果失败则从全局 chat_manager 获取
|
||||||
chat_stream = ChatStream.from_dict(chat_info)
|
chat_info = msg_dict.get("chat_info")
|
||||||
|
if chat_info and isinstance(chat_info, dict):
|
||||||
|
chat_stream = ChatStream.from_dict(chat_info)
|
||||||
|
elif self.chat_stream: # 使用实例变量中的 chat_stream
|
||||||
|
chat_stream = self.chat_stream
|
||||||
|
else: # Fallback: 尝试从 manager 获取 (可能需要 stream_id)
|
||||||
|
chat_stream = chat_manager.get_stream(self.stream_id)
|
||||||
|
if not chat_stream:
|
||||||
|
raise ValueError(f"无法确定 ChatStream for stream_id {self.stream_id}")
|
||||||
|
|
||||||
user_info = UserInfo.from_dict(msg_dict.get("user_info", {}))
|
user_info = UserInfo.from_dict(msg_dict.get("user_info", {}))
|
||||||
|
|
||||||
return Message(
|
return Message(
|
||||||
message_id=msg_dict["message_id"],
|
message_id=msg_dict.get("message_id", f"gen_{time.time()}"), # 提供默认 ID
|
||||||
chat_stream=chat_stream,
|
chat_stream=chat_stream, # 使用确定的 chat_stream
|
||||||
time=msg_dict["time"],
|
time=msg_dict.get("time", time.time()), # 提供默认时间
|
||||||
user_info=user_info,
|
user_info=user_info,
|
||||||
processed_plain_text=msg_dict.get("processed_plain_text", ""),
|
processed_plain_text=msg_dict.get("processed_plain_text", ""),
|
||||||
detailed_plain_text=msg_dict.get("detailed_plain_text", ""),
|
detailed_plain_text=msg_dict.get("detailed_plain_text", ""),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"转换消息时出错: {e}")
|
logger.warning(f"转换消息时出错: {e}")
|
||||||
raise
|
# 可以选择返回 None 或重新抛出异常,这里选择重新抛出以指示问题
|
||||||
|
raise ValueError(f"无法将字典转换为 Message 对象: {e}") from e
|
||||||
|
|
||||||
async def _handle_action(
|
async def _handle_action(
|
||||||
self, action: str, reason: str, observation_info: ObservationInfo, conversation_info: ConversationInfo
|
self, action: str, reason: str, observation_info: ObservationInfo, conversation_info: ConversationInfo
|
||||||
@@ -253,21 +267,27 @@ class Conversation:
|
|||||||
|
|
||||||
logger.info(f"执行行动: {action}, 原因: {reason}")
|
logger.info(f"执行行动: {action}, 原因: {reason}")
|
||||||
|
|
||||||
# 记录action历史,先设置为start,完成后再设置为done (这个 update 移到后面执行成功后再做)
|
# 记录action历史 (逻辑不变)
|
||||||
current_action_record = {
|
current_action_record = {
|
||||||
"action": action,
|
"action": action,
|
||||||
"plan_reason": reason, # 使用 plan_reason 存储规划原因
|
"plan_reason": reason,
|
||||||
"status": "start", # 初始状态为 start
|
"status": "start",
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
||||||
"final_reason": None,
|
"final_reason": None,
|
||||||
}
|
}
|
||||||
|
# 确保 done_action 列表存在
|
||||||
|
if not hasattr(conversation_info, 'done_action'):
|
||||||
|
conversation_info.done_action = []
|
||||||
conversation_info.done_action.append(current_action_record)
|
conversation_info.done_action.append(current_action_record)
|
||||||
# 获取刚刚添加记录的索引,方便后面更新状态
|
|
||||||
action_index = len(conversation_info.done_action) - 1
|
action_index = len(conversation_info.done_action) - 1
|
||||||
|
|
||||||
|
action_successful = False # 用于标记动作是否成功完成
|
||||||
|
|
||||||
# --- 根据不同的 action 执行 ---
|
# --- 根据不同的 action 执行 ---
|
||||||
if action == "direct_reply":
|
|
||||||
max_reply_attempts = 3 # 设置最大尝试次数(与 reply_checker.py 中的 max_retries 保持一致或稍大)
|
# send_new_message 失败后执行 wait
|
||||||
|
if action == "send_new_message":
|
||||||
|
max_reply_attempts = 3
|
||||||
reply_attempt_count = 0
|
reply_attempt_count = 0
|
||||||
is_suitable = False
|
is_suitable = False
|
||||||
need_replan = False
|
need_replan = False
|
||||||
@@ -276,179 +296,301 @@ class Conversation:
|
|||||||
|
|
||||||
while reply_attempt_count < max_reply_attempts and not is_suitable:
|
while reply_attempt_count < max_reply_attempts and not is_suitable:
|
||||||
reply_attempt_count += 1
|
reply_attempt_count += 1
|
||||||
logger.info(f"尝试生成回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...")
|
logger.info(f"尝试生成追问回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...")
|
||||||
self.state = ConversationState.GENERATING
|
self.state = ConversationState.GENERATING
|
||||||
|
|
||||||
# 1. 生成回复
|
# 1. 生成回复 (调用 generate 时传入 action_type)
|
||||||
self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info)
|
self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='send_new_message')
|
||||||
logger.info(f"第 {reply_attempt_count} 次生成的回复: {self.generated_reply}")
|
logger.info(f"第 {reply_attempt_count} 次生成的追问回复: {self.generated_reply}")
|
||||||
|
|
||||||
# 2. 检查回复
|
# 2. 检查回复 (逻辑不变)
|
||||||
self.state = ConversationState.CHECKING
|
self.state = ConversationState.CHECKING
|
||||||
try:
|
try:
|
||||||
current_goal_str = conversation_info.goal_list[0][0] if conversation_info.goal_list else ""
|
current_goal_str = conversation_info.goal_list[0][0] if conversation_info.goal_list else ""
|
||||||
# 注意:这里传递的是 reply_attempt_count - 1 作为 retry_count 给 checker
|
|
||||||
is_suitable, check_reason, need_replan = await self.reply_generator.check_reply(
|
is_suitable, check_reason, need_replan = await self.reply_generator.check_reply(
|
||||||
reply=self.generated_reply,
|
reply=self.generated_reply,
|
||||||
goal=current_goal_str,
|
goal=current_goal_str,
|
||||||
chat_history=observation_info.chat_history,
|
chat_history=observation_info.chat_history,
|
||||||
chat_history_str=observation_info.chat_history_str,
|
chat_history_str=observation_info.chat_history_str,
|
||||||
retry_count=reply_attempt_count - 1, # 传递当前尝试次数(从0开始计数)
|
retry_count=reply_attempt_count - 1,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"第 {reply_attempt_count} 次检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}"
|
f"第 {reply_attempt_count} 次追问检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_suitable:
|
if is_suitable:
|
||||||
final_reply_to_send = self.generated_reply # 保存合适的回复
|
final_reply_to_send = self.generated_reply
|
||||||
break # 回复合适,跳出循环
|
break
|
||||||
|
|
||||||
elif need_replan:
|
elif need_replan:
|
||||||
logger.warning(f"第 {reply_attempt_count} 次检查建议重新规划,停止尝试。原因: {check_reason}")
|
logger.warning(f"第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}")
|
||||||
break # 如果检查器建议重新规划,也停止尝试
|
break
|
||||||
|
|
||||||
# 如果不合适但不需要重新规划,循环会继续进行下一次尝试
|
|
||||||
except Exception as check_err:
|
except Exception as check_err:
|
||||||
logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker 时出错: {check_err}")
|
logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}")
|
||||||
check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}"
|
check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}"
|
||||||
# 如果检查本身出错,可以选择跳出循环或继续尝试
|
|
||||||
# 这里选择跳出循环,避免无限循环在检查错误上
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# 循环结束,处理最终结果
|
# 循环结束,处理最终结果
|
||||||
if is_suitable:
|
if is_suitable:
|
||||||
# 回复合适且已保存在 final_reply_to_send 中
|
# 检查是否有新消息
|
||||||
# 检查是否有新消息进来 (在所有尝试结束后再检查一次)
|
|
||||||
if self._check_new_messages_after_planning():
|
if self._check_new_messages_after_planning():
|
||||||
logger.info("生成回复期间收到新消息,取消发送,重新规划行动")
|
logger.info("生成追问回复期间收到新消息,取消发送,重新规划行动")
|
||||||
conversation_info.done_action[action_index].update(
|
conversation_info.done_action[action_index].update(
|
||||||
{
|
{"status": "recall", "final_reason": f"有新消息,取消发送追问: {final_reply_to_send}"}
|
||||||
"status": "recall",
|
|
||||||
"final_reason": f"有新消息,取消发送: {final_reply_to_send}",
|
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
# 这里直接返回,不执行后续发送和wait
|
return # 直接返回,重新规划
|
||||||
return
|
|
||||||
|
|
||||||
# 发送合适的回复
|
# 发送合适的回复
|
||||||
self.generated_reply = final_reply_to_send # 确保 self.generated_reply 是最终要发送的内容
|
self.generated_reply = final_reply_to_send
|
||||||
await self._send_reply()
|
# --- 在这里调用 _send_reply ---
|
||||||
|
await self._send_reply() # <--- 调用恢复后的函数
|
||||||
|
|
||||||
# 更新 action 历史状态为 done
|
# 更新状态: 标记上次成功是 send_new_message
|
||||||
conversation_info.done_action[action_index].update(
|
self.conversation_info.last_successful_reply_action = 'send_new_message'
|
||||||
{
|
action_successful = True # 标记动作成功
|
||||||
"status": "done",
|
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# 循环结束但没有找到合适的回复(达到最大次数或检查出错/建议重规划)
|
# 追问失败
|
||||||
logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的回复。最终原因: {check_reason}")
|
logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}")
|
||||||
conversation_info.done_action[action_index].update(
|
conversation_info.done_action[action_index].update(
|
||||||
{
|
{"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"}
|
||||||
"status": "recall", # 标记为 recall 因为没有成功发送
|
|
||||||
"final_reason": f"尝试{reply_attempt_count}次后失败: {check_reason}",
|
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
# 重置状态: 追问失败,下次用初始 prompt
|
||||||
|
self.conversation_info.last_successful_reply_action = None
|
||||||
|
|
||||||
# 执行 Wait 操作
|
# 执行 Wait 操作
|
||||||
logger.info("由于无法生成合适回复,执行 'wait' 操作...")
|
logger.info("由于无法生成合适追问回复,执行 'wait' 操作...")
|
||||||
|
self.state = ConversationState.WAITING
|
||||||
|
await self.waiter.wait(self.conversation_info)
|
||||||
|
wait_action_record = {
|
||||||
|
"action": "wait",
|
||||||
|
"plan_reason": "因 send_new_message 多次尝试失败而执行的后备等待",
|
||||||
|
"status": "done",
|
||||||
|
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
||||||
|
"final_reason": None,
|
||||||
|
}
|
||||||
|
conversation_info.done_action.append(wait_action_record)
|
||||||
|
|
||||||
|
|
||||||
|
elif action == "direct_reply":
|
||||||
|
max_reply_attempts = 3
|
||||||
|
reply_attempt_count = 0
|
||||||
|
is_suitable = False
|
||||||
|
need_replan = False
|
||||||
|
check_reason = "未进行尝试"
|
||||||
|
final_reply_to_send = ""
|
||||||
|
|
||||||
|
while reply_attempt_count < max_reply_attempts and not is_suitable:
|
||||||
|
reply_attempt_count += 1
|
||||||
|
logger.info(f"尝试生成首次回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...")
|
||||||
|
self.state = ConversationState.GENERATING
|
||||||
|
|
||||||
|
# 1. 生成回复
|
||||||
|
self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='direct_reply')
|
||||||
|
logger.info(f"第 {reply_attempt_count} 次生成的首次回复: {self.generated_reply}")
|
||||||
|
|
||||||
|
# 2. 检查回复
|
||||||
|
self.state = ConversationState.CHECKING
|
||||||
|
try:
|
||||||
|
current_goal_str = conversation_info.goal_list[0][0] if conversation_info.goal_list else ""
|
||||||
|
is_suitable, check_reason, need_replan = await self.reply_generator.check_reply(
|
||||||
|
reply=self.generated_reply,
|
||||||
|
goal=current_goal_str,
|
||||||
|
chat_history=observation_info.chat_history,
|
||||||
|
chat_history_str=observation_info.chat_history_str,
|
||||||
|
retry_count=reply_attempt_count - 1,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"第 {reply_attempt_count} 次首次回复检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}"
|
||||||
|
)
|
||||||
|
if is_suitable:
|
||||||
|
final_reply_to_send = self.generated_reply
|
||||||
|
break
|
||||||
|
elif need_replan:
|
||||||
|
logger.warning(f"第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}")
|
||||||
|
break
|
||||||
|
except Exception as check_err:
|
||||||
|
logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}")
|
||||||
|
check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}"
|
||||||
|
break
|
||||||
|
|
||||||
|
# 循环结束,处理最终结果
|
||||||
|
if is_suitable:
|
||||||
|
# 检查是否有新消息
|
||||||
|
if self._check_new_messages_after_planning():
|
||||||
|
logger.info("生成首次回复期间收到新消息,取消发送,重新规划行动")
|
||||||
|
conversation_info.done_action[action_index].update(
|
||||||
|
{"status": "recall", "final_reason": f"有新消息,取消发送首次回复: {final_reply_to_send}"}
|
||||||
|
)
|
||||||
|
return # 直接返回,重新规划
|
||||||
|
|
||||||
|
# 发送合适的回复
|
||||||
|
self.generated_reply = final_reply_to_send
|
||||||
|
# --- 在这里调用 _send_reply ---
|
||||||
|
await self._send_reply() # <--- 调用恢复后的函数
|
||||||
|
|
||||||
|
# 更新状态: 标记上次成功是 direct_reply
|
||||||
|
self.conversation_info.last_successful_reply_action = 'direct_reply'
|
||||||
|
action_successful = True # 标记动作成功
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 首次回复失败
|
||||||
|
logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的首次回复。最终原因: {check_reason}")
|
||||||
|
conversation_info.done_action[action_index].update(
|
||||||
|
{"status": "recall", "final_reason": f"首次回复尝试{reply_attempt_count}次后失败: {check_reason}"}
|
||||||
|
)
|
||||||
|
# 重置状态: 首次回复失败,下次还是用初始 prompt
|
||||||
|
self.conversation_info.last_successful_reply_action = None
|
||||||
|
|
||||||
|
# 执行 Wait 操作 (保持原有逻辑)
|
||||||
|
logger.info("由于无法生成合适首次回复,执行 'wait' 操作...")
|
||||||
self.state = ConversationState.WAITING
|
self.state = ConversationState.WAITING
|
||||||
# 直接调用 wait 方法
|
|
||||||
await self.waiter.wait(self.conversation_info)
|
await self.waiter.wait(self.conversation_info)
|
||||||
# 可以选择添加一条新的 action 记录来表示这个 wait
|
|
||||||
wait_action_record = {
|
wait_action_record = {
|
||||||
"action": "wait",
|
"action": "wait",
|
||||||
"plan_reason": "因 direct_reply 多次尝试失败而执行的后备等待",
|
"plan_reason": "因 direct_reply 多次尝试失败而执行的后备等待",
|
||||||
"status": "done", # wait 完成后可以认为是 done
|
"status": "done",
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
||||||
"final_reason": None,
|
"final_reason": None,
|
||||||
}
|
}
|
||||||
conversation_info.done_action.append(wait_action_record)
|
conversation_info.done_action.append(wait_action_record)
|
||||||
|
|
||||||
elif action == "fetch_knowledge":
|
elif action == "fetch_knowledge":
|
||||||
self.waiter.wait_accumulated_time = 0
|
|
||||||
self.state = ConversationState.FETCHING
|
self.state = ConversationState.FETCHING
|
||||||
knowledge = "TODO:知识"
|
knowledge_query = reason
|
||||||
topic = "TODO:关键词"
|
try:
|
||||||
logger.info(f"假装获取到知识{knowledge},关键词是: {topic}")
|
# 检查 knowledge_fetcher 是否存在
|
||||||
if knowledge:
|
if not hasattr(self, 'knowledge_fetcher'):
|
||||||
pass # 简单处理
|
logger.error("KnowledgeFetcher 未初始化,无法获取知识。")
|
||||||
# 标记 action 为 done
|
raise AttributeError("KnowledgeFetcher not initialized")
|
||||||
conversation_info.done_action[action_index].update(
|
|
||||||
{
|
knowledge, source = await self.knowledge_fetcher.fetch(knowledge_query, observation_info.chat_history)
|
||||||
"status": "done",
|
logger.info(f"获取到知识: {knowledge[:100]}..., 来源: {source}")
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
if knowledge:
|
||||||
}
|
# 确保 knowledge_list 存在
|
||||||
)
|
if not hasattr(conversation_info, 'knowledge_list'):
|
||||||
|
conversation_info.knowledge_list = []
|
||||||
|
conversation_info.knowledge_list.append({"query": knowledge_query, "knowledge": knowledge, "source": source})
|
||||||
|
action_successful = True
|
||||||
|
except Exception as fetch_err:
|
||||||
|
logger.error(f"获取知识时出错: {fetch_err}")
|
||||||
|
conversation_info.done_action[action_index].update(
|
||||||
|
{"status": "recall", "final_reason": f"获取知识失败: {fetch_err}"}
|
||||||
|
)
|
||||||
|
self.conversation_info.last_successful_reply_action = None # 重置状态
|
||||||
|
|
||||||
|
|
||||||
elif action == "rethink_goal":
|
elif action == "rethink_goal":
|
||||||
self.waiter.wait_accumulated_time = 0
|
|
||||||
self.state = ConversationState.RETHINKING
|
self.state = ConversationState.RETHINKING
|
||||||
await self.goal_analyzer.analyze_goal(conversation_info, observation_info)
|
try:
|
||||||
# 标记 action 为 done
|
# 检查 goal_analyzer 是否存在
|
||||||
conversation_info.done_action[action_index].update(
|
if not hasattr(self, 'goal_analyzer'):
|
||||||
{
|
logger.error("GoalAnalyzer 未初始化,无法重新思考目标。")
|
||||||
"status": "done",
|
raise AttributeError("GoalAnalyzer not initialized")
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
await self.goal_analyzer.analyze_goal(conversation_info, observation_info)
|
||||||
}
|
action_successful = True
|
||||||
)
|
except Exception as rethink_err:
|
||||||
|
logger.error(f"重新思考目标时出错: {rethink_err}")
|
||||||
|
conversation_info.done_action[action_index].update(
|
||||||
|
{"status": "recall", "final_reason": f"重新思考目标失败: {rethink_err}"}
|
||||||
|
)
|
||||||
|
self.conversation_info.last_successful_reply_action = None # 重置状态
|
||||||
|
|
||||||
|
|
||||||
elif action == "listening":
|
elif action == "listening":
|
||||||
self.state = ConversationState.LISTENING
|
self.state = ConversationState.LISTENING
|
||||||
logger.info("倾听对方发言...")
|
logger.info("倾听对方发言...")
|
||||||
await self.waiter.wait_listening(conversation_info)
|
try:
|
||||||
# listening 和 wait 通常在完成后不需要标记为 done,因为它们是持续状态,
|
# 检查 waiter 是否存在
|
||||||
# 但如果需要记录,可以在 waiter 返回后标记。目前逻辑是 waiter 返回后主循环继续。
|
if not hasattr(self, 'waiter'):
|
||||||
# 为了统一,可以暂时在这里也标记一下(或者都不标记)
|
logger.error("Waiter 未初始化,无法倾听。")
|
||||||
conversation_info.done_action[action_index].update(
|
raise AttributeError("Waiter not initialized")
|
||||||
{
|
timeout_occurred = await self.waiter.wait_listening(conversation_info)
|
||||||
"status": "done", # 或 "completed"
|
action_successful = True # Listening 完成就算成功
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
except Exception as listen_err:
|
||||||
}
|
logger.error(f"倾听时出错: {listen_err}")
|
||||||
)
|
conversation_info.done_action[action_index].update(
|
||||||
|
{"status": "recall", "final_reason": f"倾听失败: {listen_err}"}
|
||||||
|
)
|
||||||
|
self.conversation_info.last_successful_reply_action = None # 重置状态
|
||||||
|
|
||||||
|
|
||||||
elif action == "end_conversation":
|
elif action == "end_conversation":
|
||||||
self.should_continue = False # 设置循环停止标志
|
self.should_continue = False
|
||||||
logger.info("决定结束对话...")
|
logger.info("决定结束对话...")
|
||||||
# 标记 action 为 done
|
action_successful = True # 标记动作成功
|
||||||
|
|
||||||
|
elif action == "block_and_ignore":
|
||||||
|
logger.info("不想再理你了...")
|
||||||
|
ignore_duration_seconds = 10 * 60
|
||||||
|
self.ignore_until_timestamp = time.time() + ignore_duration_seconds
|
||||||
|
logger.info(f"将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}")
|
||||||
|
self.state = ConversationState.IGNORED
|
||||||
|
action_successful = True # 标记动作成功
|
||||||
|
|
||||||
|
else: # 对应 'wait' 动作
|
||||||
|
self.state = ConversationState.WAITING
|
||||||
|
logger.info("等待更多信息...")
|
||||||
|
try:
|
||||||
|
# 检查 waiter 是否存在
|
||||||
|
if not hasattr(self, 'waiter'):
|
||||||
|
logger.error("Waiter 未初始化,无法等待。")
|
||||||
|
raise AttributeError("Waiter not initialized")
|
||||||
|
timeout_occurred = await self.waiter.wait(self.conversation_info)
|
||||||
|
action_successful = True # Wait 完成就算成功
|
||||||
|
except Exception as wait_err:
|
||||||
|
logger.error(f"等待时出错: {wait_err}")
|
||||||
|
conversation_info.done_action[action_index].update(
|
||||||
|
{"status": "recall", "final_reason": f"等待失败: {wait_err}"}
|
||||||
|
)
|
||||||
|
self.conversation_info.last_successful_reply_action = None # 重置状态
|
||||||
|
|
||||||
|
# --- 更新 Action History 状态 ---
|
||||||
|
# 只有当动作本身成功时,才更新状态为 done
|
||||||
|
if action_successful:
|
||||||
conversation_info.done_action[action_index].update(
|
conversation_info.done_action[action_index].update(
|
||||||
{
|
{
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# 这里不需要 return,主循环会在下一轮检查 should_continue
|
# 重置状态: 对于非回复类动作的成功,清除上次回复状态
|
||||||
|
if action not in ['direct_reply', 'send_new_message']:
|
||||||
|
self.conversation_info.last_successful_reply_action = None
|
||||||
|
logger.debug(f"动作 {action} 成功完成,重置 last_successful_reply_action")
|
||||||
|
# 如果动作是 recall 状态,在各自的处理逻辑中已经更新了 done_action
|
||||||
|
|
||||||
elif action == "block_and_ignore":
|
async def _send_reply(self):
|
||||||
logger.info("不想再理你了...")
|
"""发送回复"""
|
||||||
# 1. 标记对话为暂时忽略
|
if not self.generated_reply:
|
||||||
ignore_duration_seconds = 10 * 60 # 10 分钟
|
logger.warning("没有生成回复内容,无法发送。")
|
||||||
self.ignore_until_timestamp = time.time() + ignore_duration_seconds
|
return
|
||||||
logger.info(f"将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}")
|
|
||||||
conversation_info.done_action[action_index].update(
|
|
||||||
{
|
|
||||||
"status": "done", # 或者一个自定义状态,比如 "ignored"
|
|
||||||
"final_reason": "Detected potential harassment, ignoring temporarily.", # 检测到潜在骚扰,暂时忽略
|
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.state = ConversationState.IGNORED
|
|
||||||
|
|
||||||
else: # 对应 'wait' 动作
|
try:
|
||||||
self.state = ConversationState.WAITING
|
_current_time = time.time()
|
||||||
logger.info("等待更多信息...")
|
reply_content = self.generated_reply
|
||||||
await self.waiter.wait(self.conversation_info)
|
|
||||||
# 同 listening,可以考虑是否标记状态
|
# 发送消息 (确保 direct_sender 和 chat_stream 有效)
|
||||||
conversation_info.done_action[action_index].update(
|
if not hasattr(self, 'direct_sender') or not self.direct_sender:
|
||||||
{
|
logger.error("DirectMessageSender 未初始化,无法发送回复。")
|
||||||
"status": "done", # 或 "completed"
|
return
|
||||||
"time": datetime.datetime.now().strftime("%H:%M:%S"),
|
if not self.chat_stream:
|
||||||
}
|
logger.error("ChatStream 未初始化,无法发送回复。")
|
||||||
)
|
return
|
||||||
|
|
||||||
|
await self.direct_sender.send_message(chat_stream=self.chat_stream, content=reply_content)
|
||||||
|
|
||||||
|
# 发送成功后,手动触发 observer 更新可能导致重复处理自己发送的消息
|
||||||
|
# 更好的做法是依赖 observer 的自动轮询或数据库触发器(如果支持)
|
||||||
|
# 暂时注释掉,观察是否影响 ObservationInfo 的更新
|
||||||
|
# self.chat_observer.trigger_update()
|
||||||
|
# if not await self.chat_observer.wait_for_update():
|
||||||
|
# logger.warning("等待 ChatObserver 更新完成超时")
|
||||||
|
|
||||||
|
self.state = ConversationState.ANALYZING # 更新状态
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"发送消息或更新状态时失败: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
self.state = ConversationState.ANALYZING
|
||||||
|
|
||||||
async def _send_timeout_message(self):
|
async def _send_timeout_message(self):
|
||||||
"""发送超时结束消息"""
|
"""发送超时结束消息"""
|
||||||
@@ -463,29 +605,3 @@ class Conversation:
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"发送超时消息失败: {str(e)}")
|
logger.error(f"发送超时消息失败: {str(e)}")
|
||||||
|
|
||||||
async def _send_reply(self):
|
|
||||||
"""发送回复"""
|
|
||||||
if not self.generated_reply:
|
|
||||||
logger.warning("没有生成回复")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 外层 try: 捕获发送消息和后续处理中的主要错误
|
|
||||||
_current_time = time.time() # 获取当前时间戳
|
|
||||||
reply_content = self.generated_reply # 获取要发送的内容
|
|
||||||
|
|
||||||
# 发送消息
|
|
||||||
await self.direct_sender.send_message(chat_stream=self.chat_stream, content=reply_content)
|
|
||||||
|
|
||||||
# 原有的触发更新和等待代码
|
|
||||||
self.chat_observer.trigger_update()
|
|
||||||
if not await self.chat_observer.wait_for_update():
|
|
||||||
logger.warning("等待 ChatObserver 更新完成超时")
|
|
||||||
|
|
||||||
self.state = ConversationState.ANALYZING # 更新对话状态
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# 这是外层 try 对应的 except
|
|
||||||
logger.error(f"发送消息或更新状态时失败: {str(e)}")
|
|
||||||
self.state = ConversationState.ANALYZING # 出错也要尝试恢复状态
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
from typing import Optional
|
||||||
class ConversationInfo:
|
class ConversationInfo:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.done_action = []
|
self.done_action = []
|
||||||
self.goal_list = []
|
self.goal_list = []
|
||||||
self.knowledge_list = []
|
self.knowledge_list = []
|
||||||
self.memory_list = []
|
self.memory_list = []
|
||||||
|
self.last_successful_reply_action: Optional[str] = None
|
||||||
|
|||||||
@@ -11,6 +11,52 @@ from src.plugins.utils.chat_message_builder import build_readable_messages
|
|||||||
|
|
||||||
logger = get_module_logger("reply_generator")
|
logger = get_module_logger("reply_generator")
|
||||||
|
|
||||||
|
# --- 定义 Prompt 模板 ---
|
||||||
|
|
||||||
|
# Prompt for direct_reply (首次回复)
|
||||||
|
PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条回复:
|
||||||
|
|
||||||
|
当前对话目标:{goals_str}
|
||||||
|
最近的聊天记录:
|
||||||
|
{chat_history_text}
|
||||||
|
|
||||||
|
|
||||||
|
请根据上述信息,结合聊天记录,回复对方。该回复应该:
|
||||||
|
1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!)
|
||||||
|
2. 符合你的性格特征和身份细节
|
||||||
|
3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况)
|
||||||
|
4. 适当利用相关知识,但不要生硬引用
|
||||||
|
5. 自然、得体,结合聊天记录逻辑合理,且没有重复表达同质内容
|
||||||
|
|
||||||
|
请注意把握聊天内容,不要回复的太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。
|
||||||
|
可以回复得自然随意自然一些,就像真人一样,注意把握聊天内容,整体风格可以平和、简短,不要刻意突出自身学科背景,不要说你说过的话,可以简短,多简短都可以,但是避免冗长。
|
||||||
|
请你注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。
|
||||||
|
不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。
|
||||||
|
|
||||||
|
请直接输出回复内容,不需要任何额外格式。"""
|
||||||
|
|
||||||
|
# Prompt for send_new_message (追问/补充)
|
||||||
|
PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊,**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息:
|
||||||
|
|
||||||
|
当前对话目标:{goals_str}
|
||||||
|
最近的聊天记录:
|
||||||
|
{chat_history_text}
|
||||||
|
|
||||||
|
|
||||||
|
请根据上述信息,结合聊天记录,继续发一条新消息(例如对之前消息的补充,深入话题,或追问等等)。该消息应该:
|
||||||
|
1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!)
|
||||||
|
2. 符合你的性格特征和身份细节
|
||||||
|
3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况)
|
||||||
|
4. 适当利用相关知识,但不要生硬引用
|
||||||
|
5. 跟之前你发的消息自然的衔接,逻辑合理,且没有重复表达同质内容或部分重叠内容
|
||||||
|
|
||||||
|
请注意把握聊天内容,不用太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。
|
||||||
|
这条消息可以自然随意自然一些,就像真人一样,注意把握聊天内容,整体风格可以平和、简短,不要刻意突出自身学科背景,不要说你说过的话,可以简短,多简短都可以,但是避免冗长。
|
||||||
|
请你注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出消息内容。
|
||||||
|
不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。
|
||||||
|
|
||||||
|
请直接输出回复内容,不需要任何额外格式。"""
|
||||||
|
|
||||||
|
|
||||||
class ReplyGenerator:
|
class ReplyGenerator:
|
||||||
"""回复生成器"""
|
"""回复生成器"""
|
||||||
@@ -28,61 +74,60 @@ class ReplyGenerator:
|
|||||||
self.chat_observer = ChatObserver.get_instance(stream_id)
|
self.chat_observer = ChatObserver.get_instance(stream_id)
|
||||||
self.reply_checker = ReplyChecker(stream_id)
|
self.reply_checker = ReplyChecker(stream_id)
|
||||||
|
|
||||||
async def generate(self, observation_info: ObservationInfo, conversation_info: ConversationInfo) -> str:
|
# 修改 generate 方法签名,增加 action_type 参数
|
||||||
|
async def generate(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str) -> str:
|
||||||
"""生成回复
|
"""生成回复
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
goal: 对话目标
|
observation_info: 观察信息
|
||||||
chat_history: 聊天历史
|
conversation_info: 对话信息
|
||||||
knowledge_cache: 知识缓存
|
action_type: 当前执行的动作类型 ('direct_reply' 或 'send_new_message')
|
||||||
previous_reply: 上一次生成的回复(如果有)
|
|
||||||
retry_count: 当前重试次数
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 生成的回复
|
str: 生成的回复
|
||||||
"""
|
"""
|
||||||
# 构建提示词
|
# 构建提示词
|
||||||
logger.debug(f"开始生成回复:当前目标: {conversation_info.goal_list}")
|
logger.debug(f"开始生成回复 (动作类型: {action_type}):当前目标: {conversation_info.goal_list}")
|
||||||
|
|
||||||
# 构建对话目标
|
# --- 构建通用 Prompt 参数 ---
|
||||||
|
# (这部分逻辑基本不变)
|
||||||
|
|
||||||
|
# 构建对话目标 (goals_str)
|
||||||
goals_str = ""
|
goals_str = ""
|
||||||
if conversation_info.goal_list:
|
if conversation_info.goal_list:
|
||||||
for goal_reason in conversation_info.goal_list:
|
for goal_reason in conversation_info.goal_list:
|
||||||
# 处理字典或元组格式
|
|
||||||
if isinstance(goal_reason, tuple):
|
if isinstance(goal_reason, tuple):
|
||||||
# 假设元组的第一个元素是目标,第二个元素是原因
|
goal = goal_reason[0] if len(goal_reason) > 0 else "目标内容缺失"
|
||||||
goal = goal_reason[0]
|
|
||||||
reasoning = goal_reason[1] if len(goal_reason) > 1 else "没有明确原因"
|
reasoning = goal_reason[1] if len(goal_reason) > 1 else "没有明确原因"
|
||||||
elif isinstance(goal_reason, dict):
|
elif isinstance(goal_reason, dict):
|
||||||
goal = goal_reason.get("goal")
|
goal = goal_reason.get("goal", "目标内容缺失")
|
||||||
reasoning = goal_reason.get("reasoning", "没有明确原因")
|
reasoning = goal_reason.get("reasoning", "没有明确原因")
|
||||||
else:
|
else:
|
||||||
# 如果是其他类型,尝试转为字符串
|
|
||||||
goal = str(goal_reason)
|
goal = str(goal_reason)
|
||||||
reasoning = "没有明确原因"
|
reasoning = "没有明确原因"
|
||||||
|
goal = str(goal) if goal is not None else "目标内容缺失"
|
||||||
goal_str = f"目标:{goal},产生该对话目标的原因:{reasoning}\n"
|
reasoning = str(reasoning) if reasoning is not None else "没有明确原因"
|
||||||
goals_str += goal_str
|
goals_str += f"- 目标:{goal}\n 原因:{reasoning}\n"
|
||||||
else:
|
else:
|
||||||
goal = "目前没有明确对话目标"
|
goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况
|
||||||
reasoning = "目前没有明确对话目标,最好思考一个对话目标"
|
|
||||||
goals_str = f"目标:{goal},产生该对话目标的原因:{reasoning}\n"
|
|
||||||
|
|
||||||
# 获取聊天历史记录
|
# 获取聊天历史记录 (chat_history_text)
|
||||||
chat_history_text = observation_info.chat_history_str
|
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
|
||||||
|
new_messages_str = await build_readable_messages(
|
||||||
|
new_messages_list,
|
||||||
|
replace_bot_name=True,
|
||||||
|
merge_messages=False,
|
||||||
|
timestamp_mode="relative",
|
||||||
|
read_mark=0.0,
|
||||||
|
)
|
||||||
|
chat_history_text += f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}"
|
||||||
|
elif not chat_history_text:
|
||||||
|
chat_history_text = "还没有聊天记录。"
|
||||||
|
|
||||||
if observation_info.new_messages_count > 0:
|
|
||||||
new_messages_list = observation_info.unprocessed_messages
|
|
||||||
new_messages_str = await build_readable_messages(
|
|
||||||
new_messages_list,
|
|
||||||
replace_bot_name=True,
|
|
||||||
merge_messages=False,
|
|
||||||
timestamp_mode="relative",
|
|
||||||
read_mark=0.0,
|
|
||||||
)
|
|
||||||
chat_history_text += f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}"
|
|
||||||
# await observation_info.clear_unprocessed_messages()
|
|
||||||
|
|
||||||
|
# 构建 Persona 文本 (persona_text)
|
||||||
identity_details_only = self.identity_detail_info
|
identity_details_only = self.identity_detail_info
|
||||||
identity_addon = ""
|
identity_addon = ""
|
||||||
if isinstance(identity_details_only, str):
|
if isinstance(identity_details_only, str):
|
||||||
@@ -97,89 +142,39 @@ class ReplyGenerator:
|
|||||||
if cleaned_details:
|
if cleaned_details:
|
||||||
identity_addon = f"并且{cleaned_details}"
|
identity_addon = f"并且{cleaned_details}"
|
||||||
persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。"
|
persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。"
|
||||||
# 构建action历史文本
|
|
||||||
action_history_list = (
|
# --- 选择 Prompt ---
|
||||||
conversation_info.done_action[-10:]
|
if action_type == 'send_new_message':
|
||||||
if len(conversation_info.done_action) >= 10
|
prompt_template = PROMPT_SEND_NEW_MESSAGE
|
||||||
else conversation_info.done_action
|
logger.info("使用 PROMPT_SEND_NEW_MESSAGE (追问生成)")
|
||||||
|
else: # 默认使用 direct_reply 的 prompt
|
||||||
|
prompt_template = PROMPT_DIRECT_REPLY
|
||||||
|
logger.info("使用 PROMPT_DIRECT_REPLY (首次/非连续回复生成)")
|
||||||
|
|
||||||
|
# --- 格式化最终的 Prompt ---
|
||||||
|
prompt = prompt_template.format(
|
||||||
|
persona_text=persona_text,
|
||||||
|
goals_str=goals_str,
|
||||||
|
chat_history_text=chat_history_text
|
||||||
)
|
)
|
||||||
action_history_text = "你之前做的事情是:"
|
|
||||||
for action in action_history_list:
|
|
||||||
if isinstance(action, dict):
|
|
||||||
action_type = action.get("action")
|
|
||||||
action_reason = action.get("reason")
|
|
||||||
action_status = action.get("status")
|
|
||||||
if action_status == "recall":
|
|
||||||
action_history_text += (
|
|
||||||
f"原本打算:{action_type},但是因为有新消息,你发现这个行动不合适,所以你没做\n"
|
|
||||||
)
|
|
||||||
elif action_status == "done":
|
|
||||||
action_history_text += f"你之前做了:{action_type},原因:{action_reason}\n"
|
|
||||||
elif isinstance(action, tuple):
|
|
||||||
# 假设元组的格式是(action_type, action_reason, action_status)
|
|
||||||
action_type = action[0] if len(action) > 0 else "未知行动"
|
|
||||||
action_reason = action[1] if len(action) > 1 else "未知原因"
|
|
||||||
action_status = action[2] if len(action) > 2 else "done"
|
|
||||||
if action_status == "recall":
|
|
||||||
action_history_text += (
|
|
||||||
f"原本打算:{action_type},但是因为有新消息,你发现这个行动不合适,所以你没做\n"
|
|
||||||
)
|
|
||||||
elif action_status == "done":
|
|
||||||
action_history_text += f"你之前做了:{action_type},原因:{action_reason}\n"
|
|
||||||
|
|
||||||
prompt = f"""{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条新消息:
|
|
||||||
|
|
||||||
当前对话目标:{goals_str}
|
|
||||||
最近的聊天记录:
|
|
||||||
{chat_history_text}
|
|
||||||
|
|
||||||
|
|
||||||
请根据上述信息,结合聊天记录,发一条消息(可以是回复,补充,深入话题,或追问等等)。该消息应该:
|
|
||||||
1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!)
|
|
||||||
2. 符合你的性格特征和身份细节
|
|
||||||
3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况)
|
|
||||||
4. 适当利用相关知识,但不要生硬引用
|
|
||||||
5. 自然、得体,结合聊天记录逻辑合理,且没有重复表达同质内容
|
|
||||||
|
|
||||||
请注意把握聊天内容,不要回复的太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。
|
|
||||||
可以回复得自然随意自然一些,就像真人一样,注意把握聊天内容,整体风格可以平和、简短,不要刻意突出自身学科背景,不要说你说过的话,可以简短,多简短都可以,但是避免冗长。
|
|
||||||
请你注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。
|
|
||||||
不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。
|
|
||||||
**注意:如果聊天记录中最新的消息是你自己发送的,那么你的思路不应该是“回复”,而是应该紧紧衔接你发送的消息,进行话题的深入,补充,或追问等等,避免与最新消息内容重叠;**
|
|
||||||
|
|
||||||
请直接输出回复内容,不需要任何额外格式。"""
|
|
||||||
|
|
||||||
|
# --- 调用 LLM 生成 ---
|
||||||
|
logger.debug(f"发送到LLM的生成提示词:\n------\n{prompt}\n------")
|
||||||
try:
|
try:
|
||||||
content, _ = await self.llm.generate_response_async(prompt)
|
content, _ = await self.llm.generate_response_async(prompt)
|
||||||
logger.info(f"生成的回复: {content}")
|
logger.info(f"生成的回复: {content}")
|
||||||
# is_new = self.chat_observer.check()
|
# 移除旧的检查新消息逻辑,这应该由 conversation 控制流处理
|
||||||
# logger.debug(f"再看一眼聊天记录,{'有' if is_new else '没有'}新消息")
|
|
||||||
|
|
||||||
# 如果有新消息,重新生成回复
|
|
||||||
# if is_new:
|
|
||||||
# logger.info("检测到新消息,重新生成回复")
|
|
||||||
# return await self.generate(
|
|
||||||
# goal, chat_history, knowledge_cache,
|
|
||||||
# None, retry_count
|
|
||||||
# )
|
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"生成回复时出错: {e}")
|
logger.error(f"生成回复时出错: {e}")
|
||||||
return "抱歉,我现在有点混乱,让我重新思考一下..."
|
return "抱歉,我现在有点混乱,让我重新思考一下..."
|
||||||
|
|
||||||
|
# check_reply 方法保持不变
|
||||||
async def 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, retry_count: int = 0
|
||||||
) -> Tuple[bool, str, bool]:
|
) -> Tuple[bool, str, bool]:
|
||||||
"""检查回复是否合适
|
"""检查回复是否合适
|
||||||
|
(此方法逻辑保持不变)
|
||||||
Args:
|
|
||||||
reply: 生成的回复
|
|
||||||
goal: 对话目标
|
|
||||||
retry_count: 当前重试次数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Tuple[bool, str, bool]: (是否合适, 原因, 是否需要重新规划)
|
|
||||||
"""
|
"""
|
||||||
return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count)
|
return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count)
|
||||||
Reference in New Issue
Block a user