加入关系判断和情绪,邪恶直推!

pull/937/head
114514 2025-05-07 01:43:31 +08:00
parent 5794774290
commit 0ed2e22360
9 changed files with 701 additions and 17 deletions

View File

@ -493,6 +493,7 @@ class BotConfig:
"llm_PFC_action_planner",
"llm_PFC_chat",
"llm_PFC_reply_checker",
"llm_PFC_relationship_eval",
]
for item in config_list:

View File

@ -22,7 +22,11 @@ logger = get_logger("pfc_action_planner")
# Prompt(1): 首次回复或非连续回复时的决策 Prompt
PROMPT_INITIAL_REPLY = """
当前时间{current_time_str}
{persona_text}现在你在参与一场QQ私聊请根据以下所有信息审慎且灵活的决策下一步行动可以回复可以倾听可以调取知识甚至可以屏蔽对方
{persona_text}
现在你正在和{sender_name}在QQ上私聊
你和对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
请根据以下所有信息审慎且灵活的决策下一步行动可以回复可以倾听可以调取知识甚至可以屏蔽对方
当前对话目标
{goals_str}
@ -58,7 +62,11 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
# Prompt(2): 上一次成功回复后,决定继续发言时的决策 Prompt
PROMPT_FOLLOW_UP = """
当前时间{current_time_str}
{persona_text}现在你在参与一场QQ私聊刚刚你已经回复了对方请根据以下所有信息审慎且灵活的决策下一步行动可以继续发送新消息可以等待可以倾听可以调取知识甚至可以屏蔽对方
{persona_text}
现在你正在和{sender_name}在QQ上私聊**并且刚刚你已经回复了对方**
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
请根据以下所有信息审慎且灵活的决策下一步行动可以继续发送新消息可以等待可以倾听可以调取知识甚至可以屏蔽对方
当前对话目标
{goals_str}
@ -170,6 +178,14 @@ class ActionPlanner:
timeout_context = self._get_timeout_context(conversation_info)
goals_str = self._build_goals_string(conversation_info)
chat_history_text = await self._build_chat_history_text(observation_info)
# 获取 sender_name, relationship_text, current_emotion_text
sender_name_str = getattr(observation_info, 'sender_name', '对方') # 从 observation_info 获取
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_text = f"你的名字是{self.name}{self.personality_info}"
action_history_summary, last_action_context = self._build_action_history_context(conversation_info)
retrieved_memory_str, retrieved_knowledge_str = await retrieve_contextual_info(
@ -212,7 +228,11 @@ 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, # 新增:传入当前时间字符串
### 标记新增/修改区域 开始 ###
sender_name=sender_name_str,
relationship_text=relationship_text_str,
current_emotion_text=current_emotion_text_str
)
logger.debug(f"[私聊][{self.private_name}] 发送到LLM的最终提示词:\n------\n{prompt}\n------")
except KeyError as fmt_key_err:

View File

@ -23,6 +23,16 @@ from ..chat.message import Message # 假设 Message 类在这里
# 导入全局配置
from ...config.config import global_config
# 导入用户信息
from ..person_info.person_info import person_info_manager
# 导入关系
from ..person_info.relationship_manager import relationship_manager
# 导入情绪
from ..moods.moods import MoodManager
from .pfc_relationship_updater import PfcRelationshipUpdater # 新增
from .pfc_emotion_updater import PfcEmotionUpdater # 新增
# 导入 PFC 内部组件和类型
from .pfc_types import ConversationState # 导入更新后的 pfc_types
from .pfc import GoalAnalyzer # 假设 GoalAnalyzer 在 pfc.py
@ -81,7 +91,14 @@ class Conversation:
self.generated_reply: str = "" # 存储最近生成的回复内容
self.chat_stream: Optional[ChatStream] = None # 关联的聊天流对象
# --- 新增:初始化管理器实例 ---
self.person_info_mng = person_info_manager
self.relationship_mng = relationship_manager
self.mood_mng = MoodManager.get_instance() # MoodManager 是单例
# 初始化所有核心组件为 None将在 _initialize 中创建
self.relationship_updater: Optional[PfcRelationshipUpdater] = None # 新增
self.emotion_updater: Optional[PfcEmotionUpdater] = None # 新增
self.action_planner: Optional[ActionPlanner] = None
self.goal_analyzer: Optional[GoalAnalyzer] = None
self.reply_generator: Optional[ReplyGenerator] = None
@ -122,6 +139,18 @@ class Conversation:
logger.debug(f"[私聊][{self.private_name}] 初始化 ActionPlanner...")
self.action_planner = ActionPlanner(self.stream_id, self.private_name)
self.relationship_updater = PfcRelationshipUpdater(
private_name=self.private_name,
bot_name=global_config.BOT_NICKNAME # 或者 self.name (如果 Conversation 类有 self.name)
)
logger.info(f"[私聊][{self.private_name}] PfcRelationshipUpdater 初始化完成。")
self.emotion_updater = PfcEmotionUpdater(
private_name=self.private_name,
bot_name=global_config.BOT_NICKNAME # 或者 self.name
)
logger.info(f"[私聊][{self.private_name}] PfcEmotionUpdater 初始化完成。")
logger.debug(f"[私聊][{self.private_name}] 初始化 GoalAnalyzer...")
self.goal_analyzer = GoalAnalyzer(self.stream_id, self.private_name)
@ -175,6 +204,66 @@ class Conversation:
# 4. 加载初始聊天记录
await self._load_initial_history()
# 4.1 加载用户数据
# 尝试从 observation_info 获取,这依赖于 _load_initial_history 的实现
private_user_id_str: Optional[str] = None
private_platform_str: Optional[str] = None
private_nickname_str: Optional[str] = None
if self.observation_info and self.observation_info.last_message_sender and self.observation_info.last_message_sender != self.bot_qq_str:
# 如果历史记录最后一条不是机器人发的,那么发送者就是对方
# 假设 observation_info 中已经有了 sender_user_id, sender_platform, sender_name
# 这些字段应该在 observation_info.py 的 update_from_message 中从非机器人消息填充
# 并且 _load_initial_history 处理历史消息时也应该填充它们
# 这里的逻辑是:取 observation_info 中最新记录的非机器人发送者的信息
if self.observation_info.sender_user_id and self.observation_info.sender_platform:
private_user_id_str = self.observation_info.sender_user_id
private_platform_str = self.observation_info.sender_platform
private_nickname_str = self.observation_info.sender_name
logger.info(f"[私聊][{self.private_name}] 从 ObservationInfo 获取到私聊对象信息: ID={private_user_id_str}, Platform={private_platform_str}, Name={private_nickname_str}")
if not private_user_id_str and self.chat_stream: # 如果 observation_info 中没有,尝试从 chat_stream (通常代表对方)
if self.chat_stream.user_info and str(self.chat_stream.user_info.user_id) != self.bot_qq_str : # 确保不是机器人自己
private_user_id_str = str(self.chat_stream.user_info.user_id)
private_platform_str = self.chat_stream.user_info.platform
private_nickname_str = self.chat_stream.user_info.user_nickname
logger.info(f"[私聊][{self.private_name}] 从 ChatStream 获取到私聊对象信息: ID={private_user_id_str}, Platform={private_platform_str}, Name={private_nickname_str}")
elif self.chat_stream.group_info is None and self.private_name: # 私聊场景,且 private_name 可能有用
# 这是一个备选方案,如果 private_name 直接是 user_id
# 你需要确认 private_name 的确切含义和格式
# logger.warning(f"[私聊][{self.private_name}] 尝试使用 private_name ('{self.private_name}') 作为 user_id平台默认为 'qq'")
# private_user_id_str = self.private_name
# private_platform_str = "qq" # 假设平台是qq
# private_nickname_str = self.private_name # 昵称也暂时用 private_name
pass # 暂时不启用此逻辑,依赖 observation_info 或 chat_stream.user_info
if private_user_id_str and private_platform_str:
try:
# 将 user_id 转换为整数类型,因为 person_info_manager.get_person_id 需要 int
private_user_id_int = int(private_user_id_str)
self.conversation_info.person_id = self.person_info_mng.get_person_id(
private_platform_str,
private_user_id_int # 使用转换后的整数ID
)
logger.info(f"[私聊][{self.private_name}] 获取到 person_id: {self.conversation_info.person_id} for {private_platform_str}:{private_user_id_str}")
# 确保用户在数据库中存在,如果不存在则创建
# get_or_create_person 内部处理 person_id 的生成,所以我们直接传 platform 和 user_id
await self.person_info_mng.get_or_create_person(
platform=private_platform_str,
user_id=private_user_id_int, # 使用转换后的整数ID
nickname=private_nickname_str if private_nickname_str else "未知用户",
# user_cardname 和 user_avatar 如果能从 chat_stream.user_info 或 observation_info 获取也应传入
# user_cardname = self.chat_stream.user_info.card if self.chat_stream and self.chat_stream.user_info else None,
# user_avatar = self.chat_stream.user_info.avatar if self.chat_stream and self.chat_stream.user_info else None
)
except ValueError:
logger.error(f"[私聊][{self.private_name}] 无法将 private_user_id_str ('{private_user_id_str}') 转换为整数。")
except Exception as e_pid:
logger.error(f"[私聊][{self.private_name}] 获取或创建 person_id 时出错: {e_pid}")
else:
logger.warning(f"[私聊][{self.private_name}] 未能确定私聊对象的 user_id 或 platform无法获取 person_id。将在收到消息后尝试。")
# 5. 启动需要后台运行的组件
logger.debug(f"[私聊][{self.private_name}] 启动 ChatObserver...")
self.chat_observer.start()
@ -183,6 +272,15 @@ class Conversation:
self.idle_conversation_starter.start()
logger.info(f"[私聊][{self.private_name}] 空闲对话检测器已启动")
# 5.1 启动 MoodManager 的后台更新
if self.mood_mng and hasattr(self.mood_mng, 'start_mood_update') and not self.mood_mng._running:
self.mood_mng.start_mood_update(update_interval=global_config.mood_update_interval) # 使用配置的更新间隔
logger.info(f"[私聊][{self.private_name}] MoodManager 已启动后台更新,间隔: {global_config.mood_update_interval} 秒。")
elif self.mood_mng and self.mood_mng._running:
logger.info(f"[私聊][{self.private_name}] MoodManager 已在运行中。")
else:
logger.warning(f"[私聊][{self.private_name}] MoodManager 未能启动,相关功能可能受限。")
# 6. 标记初始化成功并设置运行状态
self._initialized = True
self.should_continue = True # 初始化成功,标记可以继续运行循环
@ -338,6 +436,10 @@ class Conversation:
if self.observation_info and self.chat_observer:
self.observation_info.unbind_from_chat_observer()
if self.mood_mng and hasattr(self.mood_mng, 'stop_mood_update') and self.mood_mng._running:
self.mood_mng.stop_mood_update()
logger.info(f"[私聊][{self.private_name}] MoodManager 后台更新已停止。")
# ChatObserver 是单例,通常不由单个 Conversation 停止
# 如果需要,可以在管理器层面处理 ChatObserver 的生命周期
@ -411,6 +513,57 @@ class Conversation:
# --- 核心规划与行动逻辑 ---
try:
if self.conversation_info and self._initialized: # 确保 conversation_info 和实例已初始化
# 更新关系文本
if self.conversation_info.person_id and self.observation_info and self.observation_info.sender_platform and self.observation_info.sender_user_id:
try:
# relationship_manager.build_relationship_info 需要一个 (platform, user_id, user_nickname) 格式的元组
# 注意 user_id 应为 RelationshipManager 所期望的类型(通常是原始的,可能是字符串或数字,检查其内部实现)
# person_info.py 中 get_person_id 接收 int但 build_relationship_info 的 person 参数更灵活
# 这里我们假设 observation_info.sender_user_id 是字符串形式的 user_id
# 如果 RelationshipManager 内部的 get_person_id 需要 int这里可能需要转换
# 但 RelationshipManager 的 build_relationship_info 方法第一个参数 person 可以直接是 person_id (is_id=True)
# 或者是一个 (platform, user_id, nickname) 元组 (is_id=False)
# 为了安全,我们用 person_id
self.conversation_info.relationship_text = await self.relationship_mng.build_relationship_info(
self.conversation_info.person_id,
is_id=True # 明确告知是 person_id
)
logger.debug(f"[私聊][{self.private_name}] 更新关系文本: {self.conversation_info.relationship_text}")
except Exception as e_rel:
logger.error(f"[私聊][{self.private_name}] 更新关系文本时出错: {e_rel}")
self.conversation_info.relationship_text = "我们之间的关系有点微妙。" # 出错时的默认值
elif not self.conversation_info.person_id and self.observation_info and self.observation_info.sender_user_id and self.observation_info.sender_platform:
# 如果 person_id 之前没获取到,在这里尝试再次获取
try:
private_user_id_int = int(self.observation_info.sender_user_id)
self.conversation_info.person_id = self.person_info_mng.get_person_id(
self.observation_info.sender_platform,
private_user_id_int
)
await self.person_info_mng.get_or_create_person(
platform=self.observation_info.sender_platform,
user_id=private_user_id_int,
nickname=self.observation_info.sender_name if self.observation_info.sender_name else "未知用户",
)
if self.conversation_info.person_id: # 再次尝试更新关系文本
self.conversation_info.relationship_text = await self.relationship_mng.build_relationship_info(
self.conversation_info.person_id, is_id=True
)
except ValueError:
logger.error(f"[私聊][{self.private_name}] 循环中无法将 sender_user_id ('{self.observation_info.sender_user_id}') 转换为整数。")
except Exception as e_pid_loop:
logger.error(f"[私聊][{self.private_name}] 循环中获取 person_id 时出错: {e_pid_loop}")
# 更新情绪文本
if self.mood_mng:
self.conversation_info.current_emotion_text = self.mood_mng.get_prompt()
logger.debug(f"[私聊][{self.private_name}] 更新情绪文本: {self.conversation_info.current_emotion_text}")
else:
# 如果 mood_mng 未初始化,使用 ConversationInfo 中的默认值
pass # self.conversation_info.current_emotion_text 会保持其在 ConversationInfo 中的默认值
### 标记新增/修改区域 结束 ###
# 1. 检查核心组件是否都已初始化
if not all([self.action_planner, self.observation_info, self.conversation_info]):
logger.error(f"[私聊][{self.private_name}] 核心组件未初始化无法继续规划循环。将等待5秒后重试...")
@ -458,6 +611,37 @@ class Conversation:
f"[私聊][{self.private_name}] 规划期间收到新消息总数: {new_msg_count}, 来自他人: {other_new_msg_count}"
)
if self.conversation_info and other_new_msg_count > 0: # 如果有来自他人的新消息
self.conversation_info.current_instance_message_count += other_new_msg_count
logger.debug(f"[私聊][{self.private_name}] 用户发送新消息,实例消息计数增加到: {self.conversation_info.current_instance_message_count}")
# 调用增量关系更新
if self.relationship_updater:
await self.relationship_updater.update_relationship_incremental(
conversation_info=self.conversation_info,
observation_info=self.observation_info,
chat_observer_for_history=self.chat_observer
)
# 调用情绪更新
if self.emotion_updater and other_new_messages_during_planning:
# 取最后一条用户消息作为情绪更新的上下文事件
last_user_msg = other_new_messages_during_planning[-1]
last_user_msg_text = last_user_msg.get("processed_plain_text", "用户发了新消息")
sender_name_for_event = getattr(self.observation_info, 'sender_name', '对方')
if not sender_name_for_event: # 如果 observation_info 中还没有,尝试从消息中取
user_info_dict = last_user_msg.get("user_info", {})
sender_name_for_event = user_info_dict.get("user_nickname", "对方")
event_desc = f"用户【{sender_name_for_event}】发送了新消息: '{last_user_msg_text[:30]}...'"
await self.emotion_updater.update_emotion_based_on_context(
conversation_info=self.conversation_info,
observation_info=self.observation_info,
chat_observer_for_history=self.chat_observer,
event_description=event_desc
)
# 5. 根据动作类型和新消息数量,判断是否需要中断当前规划
should_interrupt: bool = False
interrupt_reason: str = ""
@ -806,7 +990,27 @@ class Conversation:
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
)
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}': 发送回复失败。")
@ -888,6 +1092,25 @@ class Conversation:
message_ids_to_clear.add(msg_id)
if message_ids_to_clear:
await observation_info.clear_processed_messages(message_ids_to_clear)
### [[[ 新增:调用关系和情绪更新 ]]] ###
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(...)
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
)
# 发送成功后结束对话
self.should_continue = False
else:
@ -906,6 +1129,14 @@ class Conversation:
# 调用 GoalAnalyzer 分析并更新目标
await self.goal_analyzer.analyze_goal(conversation_info, observation_info)
action_successful = True # 标记成功
event_for_emotion_update = "你重新思考了对话目标和方向"
if self.emotion_updater and conversation_info and observation_info: # 确保updater和info都存在
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
)
# 4. 处理倾听动作
elif action == "listening":
@ -916,6 +1147,14 @@ class Conversation:
# 调用 Waiter 的倾听等待方法,内部会处理超时
await self.waiter.wait_listening(conversation_info)
action_successful = True # listening 动作本身执行即视为成功,后续由新消息或超时驱动
event_for_emotion_update = "你决定耐心倾听对方的发言"
if self.emotion_updater and conversation_info and observation_info:
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
)
# 5. 处理结束对话动作
elif action == "end_conversation":
@ -933,6 +1172,16 @@ class Conversation:
)
self.state = ConversationState.IGNORED # 设置忽略状态
action_successful = True # 标记成功
event_for_emotion_update = "当前对话让你感到不适,你决定暂时不再理会对方"
if self.emotion_updater and conversation_info and observation_info:
# 可以让LLM判断此时的情绪或者直接设定一个倾向比如厌恶、不耐烦
# 这里还是让LLM判断
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
)
# 7. 处理等待动作
elif action == "wait":
@ -944,6 +1193,19 @@ class Conversation:
# wait 方法返回是否超时 (True=超时, False=未超时/被新消息中断)
timeout_occurred = await self.waiter.wait(self.conversation_info)
action_successful = True # wait 动作本身执行即视为成功
event_for_emotion_update = ""
if timeout_occurred: # 假设 timeout_occurred 能正确反映是否超时
event_for_emotion_update = "你等待对方回复,但对方长时间没有回应"
else:
event_for_emotion_update = "你选择等待对方的回复(对方可能很快回复了)"
if self.emotion_updater and conversation_info and observation_info:
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
)
# wait 动作完成后不需要清理消息,等待新消息或超时触发重新规划
logger.debug(f"[私聊][{self.private_name}] Wait 动作完成,无需在此清理消息。")

View File

@ -1,13 +1,20 @@
from typing import Optional
from typing import Optional, List, Dict, Any
class ConversationInfo:
def __init__(self):
self.done_action = []
self.goal_list = []
self.knowledge_list = []
self.memory_list = []
self.done_action: List[Dict[str, Any]] = [] # 建议明确类型
self.goal_list: List[Dict[str, Any]] = [] # 建议明确类型
self.knowledge_list: List[Any] = [] # 建议明确类型
self.memory_list: List[Any] = [] # 建议明确类型
self.last_successful_reply_action: Optional[str] = None
self.last_reply_rejection_reason: Optional[str] = None # 用于存储上次回复被拒原因
self.last_rejected_reply_content: Optional[str] = None # 用于存储上次被拒的回复内容
self.my_message_count: int = 0 # 用于存储连续发送了多少条消息
self.my_message_count: int = 0 # 用于存储连续发送了多少条消息
# --- 新增字段 ---
self.person_id: Optional[str] = None # 私聊对象的唯一ID
self.relationship_text: Optional[str] = "你们还不熟悉。" # 与当前对话者的关系描述文本
self.current_emotion_text: Optional[str] = "心情平静。" # 机器人当前的情绪描述文本
self.current_instance_message_count: int = 0 # 当前私聊实例中的消息计数
# --- 新增字段结束 ---

View File

@ -123,6 +123,12 @@ class ObservationInfo:
"""初始化 ObservationInfo"""
self.private_name: str = private_name
# 新增:发信人信息
self.sender_name: Optional[str] = None
self.sender_user_id: Optional[str] = None # 存储为字符串
self.sender_platform: Optional[str] = None
# 聊天记录相关
self.chat_history: List[Dict[str, Any]] = [] # 存储已处理的消息历史
self.chat_history_str: str = "还没有聊天记录。" # 用于生成 Prompt 的历史记录字符串
@ -230,13 +236,37 @@ class ObservationInfo:
if not message_time or not message_id:
logger.warning(f"[私聊][{self.private_name}] 收到的消息缺少 time 或 message_id: {message}")
return
# --- 新增/修改:提取并存储发信人详细信息 ---
current_message_sender_id: Optional[str] = None
if user_info:
try:
self.sender_user_id = str(user_info.user_id) # 确保是字符串
self.sender_name = user_info.user_nickname # 或者 user_info.card 如果私聊时card更准
self.sender_platform = user_info.platform
current_message_sender_id = self.sender_user_id # 用于后续逻辑
logger.debug(f"[私聊][{self.private_name}] 更新发信人信息: ID={self.sender_user_id}, Name={self.sender_name}, Platform={self.sender_platform}")
except AttributeError as e:
logger.error(f"[私聊][{self.private_name}] 从 UserInfo 对象提取信息时出错: {e}, UserInfo: {user_info}")
# 如果提取失败,将这些新字段设为 None避免使用旧数据
self.sender_user_id = None
self.sender_name = None
self.sender_platform = None
else:
logger.warning(f"[私聊][{self.private_name}] 处理消息更新时缺少有效的 UserInfo, message_id: {message_id}")
# 如果没有 UserInfo也将这些新字段设为 None
self.sender_user_id = None
self.sender_name = None
self.sender_platform = None
# --- 新增/修改结束 ---
# 更新最后消息时间(所有消息)
if message_time > (self.last_message_time or 0):
self.last_message_time = message_time
self.last_message_id = message_id
self.last_message_content = processed_text
self.last_message_sender = sender_id_str
self.last_message_sender = current_message_sender_id # 使用新获取的 current_message_sender_id
# 更新说话者特定时间
if sender_id_str:

View File

@ -0,0 +1,110 @@
# PFC/pfc_emotion_updater.py
from typing import List, Dict, Any, Optional
from src.common.logger_manager import get_logger
from src.plugins.models.utils_model import LLMRequest
from src.plugins.moods.moods import MoodManager # MoodManager 本身是单例
from src.plugins.utils.chat_message_builder import build_readable_messages
try:
from .observation_info import ObservationInfo
from .conversation_info import ConversationInfo
except ImportError:
from observation_info import ObservationInfo
from conversation_info import ConversationInfo
from ...config.config import global_config # 导入全局配置
logger = get_logger("pfc_emotion_updater")
class PfcEmotionUpdater:
def __init__(self, private_name: str, bot_name: str):
"""
初始化情绪更新器
"""
self.private_name = private_name
self.bot_name = bot_name
self.mood_mng = MoodManager.get_instance() # 获取 MoodManager 单例
# LLM 实例 (根据 global_config.llm_summary 配置)
llm_config_summary = getattr(global_config, 'llm_summary', None)
if llm_config_summary and isinstance(llm_config_summary, dict):
logger.info(f"[私聊][{self.private_name}] 使用 llm_summary 配置初始化情绪判断LLM。")
self.llm = LLMRequest(
model=llm_config_summary,
temperature=llm_config_summary.get("temperature", 0.5), # temperature 来自其自身配置或默认0.7这里用0.5
max_tokens=llm_config_summary.get("max_tokens", 256), # 情绪词输出不需要很多token
request_type="pfc_emotion_evaluation"
)
else:
logger.error(f"[私聊][{self.private_name}] 未找到 llm_summary 配置或配置无效!情绪判断功能将受限。")
self.llm = None # LLM 未初始化
self.EMOTION_UPDATE_INTENSITY = getattr(global_config, 'pfc_emotion_update_intensity', 0.6)
self.EMOTION_HISTORY_COUNT = getattr(global_config, 'pfc_emotion_history_count', 5)
async def update_emotion_based_on_context(
self,
conversation_info: ConversationInfo,
observation_info: ObservationInfo,
chat_observer_for_history, # ChatObserver 实例
event_description: str
) -> None:
if not self.llm:
logger.error(f"[私聊][{self.private_name}] LLM未初始化无法进行情绪更新。")
# 即使LLM失败也应该更新conversation_info中的情绪文本为MoodManager的当前状态
if conversation_info and self.mood_mng:
conversation_info.current_emotion_text = self.mood_mng.get_prompt()
return
if not self.mood_mng or not conversation_info or not observation_info:
logger.debug(f"[私聊][{self.private_name}] 情绪更新:缺少必要管理器或信息。")
return
recent_messages_for_emotion: List[Dict[str, Any]] = []
if chat_observer_for_history:
recent_messages_for_emotion = chat_observer_for_history.get_cached_messages(limit=self.EMOTION_HISTORY_COUNT)
elif observation_info.chat_history:
recent_messages_for_emotion = observation_info.chat_history[-self.EMOTION_HISTORY_COUNT:]
readable_recent_history = await build_readable_messages(
recent_messages_for_emotion, replace_bot_name=True, merge_messages=True, timestamp_mode="none"
)
current_mood_text_from_manager = self.mood_mng.current_mood.text # 从 MoodManager 获取当前情绪文本
sender_name_for_prompt = getattr(observation_info, 'sender_name', '对方')
if not sender_name_for_prompt: sender_name_for_prompt = '对方'
relationship_text_for_prompt = getattr(conversation_info, 'relationship_text', '关系一般。') # 从 ConversationInfo 获取关系文本
emotion_prompt = f"""你是机器人 {self.bot_name}。你现在的心情是【{current_mood_text_from_manager}】。
你正在和用户{sender_name_for_prompt}私聊你们的关系是{relationship_text_for_prompt}
最近发生的事件是{event_description}
最近的对话摘要
---
{readable_recent_history}
---
基于以上所有信息你认为你现在最主要的情绪是什么请从以下情绪词中选择一个必须是列表中的一个
[开心, 害羞, 愤怒, 恐惧, 悲伤, 厌恶, 惊讶, 困惑, 平静]
请只输出一个最符合的情绪词例如 开心
如果难以判断或当前情绪依然合适请输出 无变化
"""
try:
logger.debug(f"[私聊][{self.private_name}] 情绪判断Prompt:\n{emotion_prompt}")
content, _ = await self.llm.generate_response_async(emotion_prompt)
detected_emotion_word = content.strip().replace("\"", "").replace("'", "")
logger.debug(f"[私聊][{self.private_name}] 情绪判断LLM原始返回: '{detected_emotion_word}'")
if detected_emotion_word and detected_emotion_word != "无变化" and detected_emotion_word in self.mood_mng.emotion_map:
self.mood_mng.update_mood_from_emotion(detected_emotion_word, intensity=self.EMOTION_UPDATE_INTENSITY)
logger.info(f"[私聊][{self.private_name}] 基于事件 '{event_description}',情绪已更新为倾向于 '{detected_emotion_word}'。当前心情: {self.mood_mng.current_mood.text}")
elif detected_emotion_word == "无变化":
logger.info(f"[私聊][{self.private_name}] 基于事件 '{event_description}'LLM判断情绪无显著变化。")
else:
logger.warning(f"[私聊][{self.private_name}] LLM返回了未知的情绪词 '{detected_emotion_word}' 或未返回有效词,情绪未主动更新。")
except Exception as e:
logger.error(f"[私聊][{self.private_name}] 情绪判断LLM调用或处理失败: {e}")
# 无论LLM判断如何都更新conversation_info中的情绪文本以供Prompt使用
if conversation_info and self.mood_mng: # 确保conversation_info有效
conversation_info.current_emotion_text = self.mood_mng.get_prompt()

View File

@ -0,0 +1,219 @@
from typing import List, Dict, Any, Optional
from src.common.logger_manager import get_logger
from src.plugins.models.utils_model import LLMRequest
from src.plugins.person_info.person_info import person_info_manager
from src.plugins.person_info.relationship_manager import relationship_manager # 主要用其 ensure_float 和 build_relationship_info
from src.plugins.utils.chat_message_builder import build_readable_messages
# 从PFC内部导入必要的类 (根据你的项目结构调整当前假设与PFC核心文件同级或有正确路径)
try:
from .observation_info import ObservationInfo
from .conversation_info import ConversationInfo
from .pfc_utils import get_items_from_json
except ImportError: # 如果直接从 plugins/PFC 目录运行,上面的相对导入会失败,尝试绝对导入
from observation_info import ObservationInfo
from conversation_info import ConversationInfo
from pfc_utils import get_items_from_json
from ...config.config import global_config # 导入全局配置 (向上两级到 src/, 再到 config)
logger = get_logger("pfc_relationship_updater")
class PfcRelationshipUpdater:
def __init__(self, private_name: str, bot_name: str):
"""
初始化关系更新器
Args:
private_name (str): 当前私聊对象的名称 (用于日志)
bot_name (str): 机器人自己的名称
"""
self.private_name = private_name
self.bot_name = bot_name
self.person_info_mng = person_info_manager
self.relationship_mng = relationship_manager # 复用其实例方法
# LLM 实例 (为关系评估创建一个新的)
# 尝试读取 llm_PFC_relationship_eval 配置,如果不存在则回退
llm_config_rel_eval = getattr(global_config, 'llm_PFC_relationship_eval', None)
if llm_config_rel_eval and isinstance(llm_config_rel_eval, dict):
logger.info(f"[私聊][{self.private_name}] 使用 llm_PFC_relationship_eval 配置初始化关系评估LLM。")
self.llm = LLMRequest(
model=llm_config_rel_eval,
temperature=llm_config_rel_eval.get("temp", 0.5), # 判断任务通常用较低温度
max_tokens=llm_config_rel_eval.get("max_tokens", 512),
request_type="pfc_relationship_evaluation"
)
else:
logger.warning(f"[私聊][{self.private_name}] 未找到 llm_PFC_relationship_eval 配置或配置无效,将回退使用 llm_PFC_action_planner 的配置。")
llm_config_action_planner = getattr(global_config, 'llm_PFC_action_planner', None)
if llm_config_action_planner and isinstance(llm_config_action_planner, dict):
self.llm = LLMRequest(
model=llm_config_action_planner, # 使用 action_planner 的模型配置
temperature=llm_config_action_planner.get("temp", 0.5), # 但温度可以尝试低一些
max_tokens=llm_config_action_planner.get("max_tokens", 512),
request_type="pfc_relationship_evaluation_fallback"
)
else: # 极端情况,连 action_planner 的配置都没有
logger.error(f"[私聊][{self.private_name}] 无法找到任何有效的LLM配置用于关系评估关系更新功能将受限。")
self.llm = None # LLM 未初始化
# 从 global_config 读取参数,若无则使用默认值
self.REL_INCREMENTAL_INTERVAL = getattr(global_config, 'pfc_relationship_incremental_interval', 10)
self.REL_INCREMENTAL_MSG_COUNT = getattr(global_config, 'pfc_relationship_incremental_msg_count', 10)
self.REL_INCREMENTAL_DEFAULT_CHANGE = getattr(global_config, 'pfc_relationship_incremental_default_change', 1.0)
self.REL_INCREMENTAL_MAX_CHANGE = getattr(global_config, 'pfc_relationship_incremental_max_change', 5.0)
self.REL_FINAL_MSG_COUNT = getattr(global_config, 'pfc_relationship_final_msg_count', 30)
self.REL_FINAL_DEFAULT_CHANGE = getattr(global_config, 'pfc_relationship_final_default_change', 5.0)
self.REL_FINAL_MAX_CHANGE = getattr(global_config, 'pfc_relationship_final_max_change', 50.0)
async def update_relationship_incremental(
self,
conversation_info: ConversationInfo,
observation_info: ObservationInfo,
chat_observer_for_history # ChatObserver 实例
) -> None:
if not self.llm:
logger.error(f"[私聊][{self.private_name}] LLM未初始化无法进行增量关系更新。")
return
if not conversation_info or not conversation_info.person_id or not observation_info:
logger.debug(f"[私聊][{self.private_name}] 增量关系更新:缺少必要信息。")
return
if not (conversation_info.current_instance_message_count % self.REL_INCREMENTAL_INTERVAL == 0 \
and conversation_info.current_instance_message_count > 0):
return
logger.info(f"[私聊][{self.private_name}] 达到增量关系更新阈值 ({conversation_info.current_instance_message_count}条消息),开始评估...")
messages_for_eval: List[Dict[str, Any]] = []
if chat_observer_for_history:
messages_for_eval = chat_observer_for_history.get_cached_messages(limit=self.REL_INCREMENTAL_MSG_COUNT)
elif observation_info.chat_history:
messages_for_eval = observation_info.chat_history[-self.REL_INCREMENTAL_MSG_COUNT:]
if not messages_for_eval:
logger.warning(f"[私聊][{self.private_name}] 增量关系更新:没有足够的消息进行评估。")
return
readable_history_for_llm = await build_readable_messages(
messages_for_eval, replace_bot_name=True, merge_messages=False, timestamp_mode="relative"
)
current_relationship_value = await self.person_info_mng.get_value(conversation_info.person_id, "relationship_value")
current_relationship_value = self.relationship_mng.ensure_float(current_relationship_value, conversation_info.person_id)
sender_name_for_prompt = getattr(observation_info, 'sender_name', '对方')
if not sender_name_for_prompt: sender_name_for_prompt = '对方'
relationship_prompt = f"""你是{self.bot_name}。你正在与{sender_name_for_prompt}私聊。
你们当前的关系值大约是 {current_relationship_value:.0f} (范围通常在-1000到1000越高越代表关系越好)
以下是你们最近的对话内容
---
{readable_history_for_llm}
---
请基于以上对话判断你与{sender_name_for_prompt}的关系值应该如何谨慎地调整
请输出一个JSON对象包含一个 "adjustment" 字段其值为一个介于 -{self.REL_INCREMENTAL_MAX_CHANGE} +{self.REL_INCREMENTAL_MAX_CHANGE} 之间的整数代表关系值的变化
例如{{ "adjustment": 3 }}如果对话内容不明确或难以判断请倾向于输出较小的调整值如0, 1, -1"""
adjustment_val = self.REL_INCREMENTAL_DEFAULT_CHANGE
try:
logger.debug(f"[私聊][{self.private_name}] 增量关系评估Prompt:\n{relationship_prompt}")
content, _ = await self.llm.generate_response_async(relationship_prompt)
logger.debug(f"[私聊][{self.private_name}] 增量关系评估LLM原始返回: {content}")
success, result = get_items_from_json(
content, self.private_name, "adjustment",
default_values={"adjustment": self.REL_INCREMENTAL_DEFAULT_CHANGE},
required_types={"adjustment": (int, float)}
)
raw_adjustment = result.get("adjustment", self.REL_INCREMENTAL_DEFAULT_CHANGE)
if not isinstance(raw_adjustment, (int, float)):
adjustment_val = self.REL_INCREMENTAL_DEFAULT_CHANGE
else:
adjustment_val = float(raw_adjustment)
adjustment_val = max(-self.REL_INCREMENTAL_MAX_CHANGE, min(self.REL_INCREMENTAL_MAX_CHANGE, adjustment_val))
except Exception as e:
logger.error(f"[私聊][{self.private_name}] 增量关系评估LLM调用或解析失败: {e}")
new_relationship_value = max(-1000.0, min(1000.0, current_relationship_value + adjustment_val))
await self.person_info_mng.update_one_field(conversation_info.person_id, "relationship_value", new_relationship_value)
logger.info(f"[私聊][{self.private_name}] 增量关系值更新:与【{sender_name_for_prompt}】的关系值从 {current_relationship_value:.2f} 调整了 {adjustment_val:.2f},变为 {new_relationship_value:.2f}")
if conversation_info.person_id:
conversation_info.relationship_text = await self.relationship_mng.build_relationship_info(conversation_info.person_id, is_id=True)
async def update_relationship_final(
self,
conversation_info: ConversationInfo,
observation_info: ObservationInfo,
chat_observer_for_history
) -> None:
if not self.llm:
logger.error(f"[私聊][{self.private_name}] LLM未初始化无法进行最终关系更新。")
return
if not conversation_info or not conversation_info.person_id or not observation_info:
logger.debug(f"[私聊][{self.private_name}] 最终关系更新:缺少必要信息。")
return
logger.info(f"[私聊][{self.private_name}] 私聊结束,开始最终关系评估...")
messages_for_eval: List[Dict[str, Any]] = []
if chat_observer_for_history:
messages_for_eval = chat_observer_for_history.get_cached_messages(limit=self.REL_FINAL_MSG_COUNT)
elif observation_info.chat_history:
messages_for_eval = observation_info.chat_history[-self.REL_FINAL_MSG_COUNT:]
if not messages_for_eval:
logger.warning(f"[私聊][{self.private_name}] 最终关系更新:没有足够的消息进行评估。")
return
readable_history_for_llm = await build_readable_messages(
messages_for_eval, replace_bot_name=True, merge_messages=False, timestamp_mode="relative"
)
current_relationship_value = await self.person_info_mng.get_value(conversation_info.person_id, "relationship_value")
current_relationship_value = self.relationship_mng.ensure_float(current_relationship_value, conversation_info.person_id)
sender_name_for_prompt = getattr(observation_info, 'sender_name', '对方')
if not sender_name_for_prompt: sender_name_for_prompt = '对方'
relationship_prompt = f"""你是{self.bot_name}。你与{sender_name_for_prompt}的私聊刚刚结束。
你们当前的关系值大约是 {current_relationship_value:.0f} (范围通常在-1000到1000越高越好)
以下是你们本次私聊最后部分的对话内容
---
{readable_history_for_llm}
---
请基于以上对话的整体情况判断你与{sender_name_for_prompt}的关系值应该如何进行一次总结性的调整
请输出一个JSON对象包含一个 "final_adjustment" 字段其值为一个整数代表关系值的变化量例如可以是 -{self.REL_FINAL_MAX_CHANGE} +{self.REL_FINAL_MAX_CHANGE} 之间的一个值
请大胆评估但也要合理"""
adjustment_val = self.REL_FINAL_DEFAULT_CHANGE
try:
logger.debug(f"[私聊][{self.private_name}] 最终关系评估Prompt:\n{relationship_prompt}")
content, _ = await self.llm.generate_response_async(relationship_prompt)
logger.debug(f"[私聊][{self.private_name}] 最终关系评估LLM原始返回: {content}")
success, result = get_items_from_json(
content, self.private_name, "final_adjustment",
default_values={"final_adjustment": self.REL_FINAL_DEFAULT_CHANGE},
required_types={"final_adjustment": (int, float)}
)
raw_adjustment = result.get("final_adjustment", self.REL_FINAL_DEFAULT_CHANGE)
if not isinstance(raw_adjustment, (int, float)):
adjustment_val = self.REL_FINAL_DEFAULT_CHANGE
else:
adjustment_val = float(raw_adjustment)
adjustment_val = max(-self.REL_FINAL_MAX_CHANGE, min(self.REL_FINAL_MAX_CHANGE, adjustment_val))
except Exception as e:
logger.error(f"[私聊][{self.private_name}] 最终关系评估LLM调用或解析失败: {e}")
new_relationship_value = max(-1000.0, min(1000.0, current_relationship_value + adjustment_val))
await self.person_info_mng.update_one_field(conversation_info.person_id, "relationship_value", new_relationship_value)
logger.info(f"[私聊][{self.private_name}] 最终关系值更新:与【{sender_name_for_prompt}】的关系值从 {current_relationship_value:.2f} 调整了 {adjustment_val:.2f},最终为 {new_relationship_value:.2f}")
if conversation_info.person_id: # 虽然通常结束了,但更新一下无妨
conversation_info.relationship_text = await self.relationship_mng.build_relationship_info(conversation_info.person_id, is_id=True)

View File

@ -23,7 +23,11 @@ logger = get_logger("reply_generator")
# Prompt for direct_reply (首次回复)
PROMPT_DIRECT_REPLY = """
当前时间{current_time_str}
{persona_text}现在你在参与一场QQ私聊请根据以下信息生成一条回复
{persona_text}
你正在和{sender_name}在QQ上私聊
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
请根据以下信息生成一条回复
当前对话目标{goals_str}
@ -56,7 +60,11 @@ PROMPT_DIRECT_REPLY = """
# Prompt for send_new_message (追问/补充)
PROMPT_SEND_NEW_MESSAGE = """
当前时间{current_time_str}
{persona_text}现在你在参与一场QQ私聊**刚刚你已经发送了一条或多条消息**现在请根据以下信息再发一条新消息
{persona_text}
你正在和{sender_name}在QQ上私聊**并且刚刚你已经发送了一条或多条消息**
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
现在请根据以下信息再发一条新消息
当前对话目标{goals_str}
@ -88,7 +96,11 @@ PROMPT_SEND_NEW_MESSAGE = """
# Prompt for say_goodbye (告别语生成)
PROMPT_FAREWELL = """
当前时间{current_time_str}
{persona_text}你在参与一场 QQ 私聊现在对话似乎已经结束你决定再发一条最后的消息来圆满结束
{persona_text}
你正在和{sender_name}私聊在QQ上私聊现在你们的对话似乎已经结束
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
现在你决定再发一条最后的消息来圆满结束
最近的聊天记录
{chat_history_text}
@ -179,6 +191,14 @@ class ReplyGenerator:
chat_history_text += (
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
@ -231,7 +251,10 @@ class ReplyGenerator:
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:
@ -242,7 +265,10 @@ class ReplyGenerator:
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 # 新增:传入当前时间字符串
current_time_str=current_time_value, # 新增:传入当前时间字符串
sender_name=sender_name_str,
relationship_text=relationship_text_str,
current_emotion_text=current_emotion_text_str
)
except KeyError as e:
logger.error(

View File

@ -1,5 +1,5 @@
[inner]
version = "1.6.3"
version = "1.6.4"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请在修改后将version的值进行变更
@ -297,6 +297,15 @@ provider = "SILICONFLOW"
pri_in = 2
pri_out = 8
# PFC 关系评估LLM
[model.llm_PFC_relationship_eval]
name = "Pro/deepseek-ai/DeepSeek-V3" # 或者其他你认为适合判断任务的模型
provider = "SILICONFLOW"
temp = 0.4 # 判断任务建议温度稍低
max_tokens = 512 # 根据Prompt长度和期望输出来调整
pri_in = 2 # 价格信息(可选)
pri_out = 8 # 价格信息(可选)
#以下模型暂时没有使用!!
#以下模型暂时没有使用!!
#以下模型暂时没有使用!!