Merge branch 'PFC-test' of https://github.com/smartmita/MaiBot into G-Test

pull/937/head
Bakadax 2025-05-03 22:12:02 +08:00
commit cb9b4423d8
4 changed files with 215 additions and 114 deletions

View File

@ -1,5 +1,14 @@
import time
from typing import Tuple, Optional # 增加了 Optional
from typing import Tuple, Optional
from src.plugins.memory_system.Hippocampus import HippocampusManager
# --- NEW IMPORT ---
# 从 heartflow 导入知识检索和数据库查询函数/实例
from src.plugins.heartFC_chat.heartflow_prompt_builder import prompt_builder
# --- END NEW IMPORT ---
# import jieba # 如果需要旧版知识库的回退,可能需要
# import re # 如果需要旧版知识库的回退,可能需要
from src.common.logger_manager import get_logger
from ..models.utils_model import LLMRequest
from ...config.config import global_config
@ -21,20 +30,21 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊
当前对话目标
{goals_str}
{knowledge_info_str}
最近行动历史概要
{action_history_summary}
你想起来的相关知识
{retrieved_knowledge_str}
上一次行动的详细情况和结果
{last_action_context}
时间和超时提示
{time_since_last_bot_message_info}{timeout_context}
最近的对话记录(包括你已成功发送的消息 新收到的消息)
{chat_history_text}
你的的回忆
{retrieved_memory_str}
------
可选行动类型以及解释
fetch_knowledge: 需要调取知识或记忆当需要专业知识或特定信息时选择对方若提到你不太认识的人名或实体也可以尝试选择
listening: 倾听对方发言当你认为对方话才说到一半发言明显未结束时选择
direct_reply: 直接回复对方
rethink_goal: 思考一个对话目标当你觉得目前对话需要目标或当前目标不再适用或话题卡住时选择注意私聊的环境是灵活的有可能需要经常选择
@ -50,24 +60,24 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
注意请严格按照JSON格式输出不要包含任何其他内容"""
# Prompt(2): 上一次成功回复后,决定继续发言时的决策 Prompt
PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊刚刚你已经回复了对方请根据以下【所有信息】审慎且灵活的决策下一步行动可以继续发送新消息可以等待可以倾听可以调取知识甚至可以屏蔽对方
PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊刚刚你已经回复了对方请根据以下【所有信息】审慎且灵活的决策下一步行动可以继续发送新消息可以等待可以倾听可以调取知识甚至可以屏蔽对方
当前对话目标
{goals_str}
{knowledge_info_str}
最近行动历史概要
{action_history_summary}
你想起来的相关知识
{retrieved_knowledge_str}
上一次行动的详细情况和结果
{last_action_context}
时间和超时提示
{time_since_last_bot_message_info}{timeout_context}
{time_since_last_bot_message_info}{timeout_context}
最近的对话记录(包括你已成功发送的消息 新收到的消息)
{chat_history_text}
你的的回忆
{retrieved_memory_str}
------
可选行动类型以及解释
fetch_knowledge: 需要调取知识当需要专业知识或特定信息时选择对方若提到你不太认识的人名或实体也可以尝试选择
wait: 暂时不说话留给对方交互空间等待对方回复尤其是在你刚发言后或上次发言因重复发言过多被拒时或不确定做什么时这是不错的选择
listening: 倾听对方发言虽然你刚发过言但如果对方立刻回复且明显话没说完可以选择这个
send_new_message: 发送一条新消息继续对话允许适当的追问补充深入话题或开启相关新话题**但是避免在因重复被拒后立即使用也不要在对方没有回复的情况下过多的消息轰炸或重复发言**
@ -117,7 +127,41 @@ class ActionPlanner:
self.name = global_config.BOT_NICKNAME
self.private_name = private_name
self.chat_observer = ChatObserver.get_instance(stream_id, private_name)
# self.action_planner_info = ActionPlannerInfo() # 移除未使用的变量
# _get_memory_info 保持不变
async def _get_memory_info(self, text: str) -> str:
"""根据文本自动检索相关记忆"""
memory_prompt = ""
related_memory_info = ""
try:
related_memory = await HippocampusManager.get_instance().get_memory_from_text(
text=text,
max_memory_num=2, # 最多获取 2 条记忆
max_memory_length=2, # 每条记忆长度限制(这个参数含义可能需确认)
max_depth=3, # 搜索深度
fast_retrieval=False, # 是否快速检索
)
if related_memory:
for memory in related_memory:
# memory[0] 是记忆ID, memory[1] 是记忆内容
related_memory_info += memory[1] + "\n" # 将记忆内容拼接起来
if related_memory_info:
memory_prompt = f"你回忆起:\n{related_memory_info.strip()}\n(以上是你的回忆,供参考)\n"
logger.debug(
f"[私聊]决策层[{self.private_name}]自动检索到记忆: {related_memory_info.strip()[:100]}..."
)
else:
logger.debug(f"[私聊]决策层[{self.private_name}]自动检索记忆返回为空。")
else:
logger.debug(f"[私聊]决策层[{self.private_name}]未自动检索到相关记忆。")
except Exception as e:
logger.error(f"[私聊]决策层[{self.private_name}]自动检索记忆时出错: {e}")
# memory_prompt = "检索记忆时出错。\n" # 可以选择是否提示错误
return memory_prompt
# --- REMOVED _get_prompt_info_old ---
# --- REMOVED _get_prompt_info ---
# 修改 plan 方法签名,增加 last_successful_reply_action 参数
async def plan(
@ -226,41 +270,6 @@ class ActionPlanner:
logger.error(f"[私聊][{self.private_name}]构建对话目标字符串时出错: {e}")
goals_str = "- 构建对话目标时出错。\n"
# --- 知识信息字符串构建开始 ---
knowledge_info_str = "【已获取的相关知识和记忆】\n"
try:
# 检查 conversation_info 是否有 knowledge_list 并且不为空
if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list:
# 最多只显示最近的 5 条知识,防止 Prompt 过长
recent_knowledge = conversation_info.knowledge_list[-5:]
for i, knowledge_item in enumerate(recent_knowledge):
if isinstance(knowledge_item, dict):
query = knowledge_item.get("query", "未知查询")
knowledge = knowledge_item.get("knowledge", "无知识内容")
source = knowledge_item.get("source", "未知来源")
# 只取知识内容的前 2000 个字,避免太长
knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 2000 else knowledge
knowledge_info_str += (
f"{i + 1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n"
)
else:
# 处理列表里不是字典的异常情况
knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n"
if not recent_knowledge: # 如果 knowledge_list 存在但为空
knowledge_info_str += "- 暂无相关知识和记忆。\n"
else:
# 如果 conversation_info 没有 knowledge_list 属性,或者列表为空
knowledge_info_str += "- 暂无相关知识记忆。\n"
except AttributeError:
logger.warning(f"[私聊][{self.private_name}]ConversationInfo 对象可能缺少 knowledge_list 属性。")
knowledge_info_str += "- 获取知识列表时出错。\n"
except Exception as e:
logger.error(f"[私聊][{self.private_name}]构建知识信息字符串时出错: {e}")
knowledge_info_str += "- 处理知识列表时出错。\n"
# --- 知识信息字符串构建结束 ---
# 获取聊天历史记录 (chat_history_text)
try:
if hasattr(observation_info, "chat_history") and observation_info.chat_history:
@ -368,6 +377,39 @@ class ActionPlanner:
last_action_context += f"- 该行动当前状态: {status}\n"
# self.last_successful_action_type = None # 非完成状态,清除记录
retrieved_memory_str_planner = ""
retrieved_knowledge_str_planner = ""
retrieval_context = chat_history_text # 使用聊天记录作为检索上下文
if retrieval_context and retrieval_context != "还没有聊天记录。" and retrieval_context != "[构建聊天记录出错]":
try:
# 调用本地的 _get_memory_info
logger.debug(f"[私聊][{self.private_name}] (ActionPlanner) 开始自动检索记忆...")
retrieved_memory_str_planner = await self._get_memory_info(text=retrieval_context)
logger.info(
f"[私聊][{self.private_name}] (ActionPlanner) 自动检索记忆 {'完成' if retrieved_memory_str_planner else '无结果'}"
)
# --- MODIFIED KNOWLEDGE RETRIEVAL ---
# 调用导入的 prompt_builder.get_prompt_info
logger.debug(f"[私聊][{self.private_name}] (ActionPlanner) 开始自动检索知识 (使用导入函数)...")
# 使用导入的 prompt_builder 实例及其方法
retrieved_knowledge_str_planner = await prompt_builder.get_prompt_info(
message=retrieval_context, threshold=0.38
)
# --- END MODIFIED KNOWLEDGE RETRIEVAL ---
logger.info(
f"[私聊][{self.private_name}] (ActionPlanner) 自动检索知识 {'完成' if retrieved_knowledge_str_planner else '无结果'}"
)
except Exception as retrieval_err:
logger.error(f"[私聊][{self.private_name}] (ActionPlanner) 自动检索时出错: {retrieval_err}")
retrieved_memory_str_planner = "检索记忆时出错。\n"
retrieved_knowledge_str_planner = "检索知识时出错。\n"
else:
logger.debug(f"[私聊][{self.private_name}] (ActionPlanner) 无有效聊天记录,跳过自动检索。")
retrieved_memory_str_planner = "无聊天记录无法检索记忆。\n"
retrieved_knowledge_str_planner = "无聊天记录无法检索知识。\n"
# --- 选择 Prompt ---
if last_successful_reply_action in ["direct_reply", "send_new_message"]:
prompt_template = PROMPT_FOLLOW_UP
@ -385,7 +427,11 @@ class ActionPlanner:
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 "还没有聊天记录。",
knowledge_info_str=knowledge_info_str,
# knowledge_info_str=knowledge_info_str, # 移除了旧知识展示方式
retrieved_memory_str=retrieved_memory_str_planner if retrieved_memory_str_planner else "无相关记忆。",
retrieved_knowledge_str=retrieved_knowledge_str_planner
if retrieved_knowledge_str_planner
else "无相关知识。",
)
logger.debug(f"[私聊][{self.private_name}]发送到LLM的最终提示词:\n------\n{prompt}\n------")
@ -468,7 +514,6 @@ class ActionPlanner:
valid_actions = [
"direct_reply",
"send_new_message",
"fetch_knowledge",
"wait",
"listening",
"rethink_goal",

View File

@ -508,31 +508,31 @@ class Conversation:
}
conversation_info.done_action.append(wait_action_record)
elif action == "fetch_knowledge":
self.state = ConversationState.FETCHING
knowledge_query = reason
try:
# 检查 knowledge_fetcher 是否存在
if not hasattr(self, "knowledge_fetcher"):
logger.error(f"[私聊][{self.private_name}]KnowledgeFetcher 未初始化,无法获取知识。")
raise AttributeError("KnowledgeFetcher not initialized")
# elif action == "fetch_knowledge":
# self.state = ConversationState.FETCHING
# knowledge_query = reason
# try:
# 检查 knowledge_fetcher 是否存在
# if not hasattr(self, "knowledge_fetcher"):
# logger.error(f"[私聊][{self.private_name}]KnowledgeFetcher 未初始化,无法获取知识。")
# raise AttributeError("KnowledgeFetcher not initialized")
knowledge, source = await self.knowledge_fetcher.fetch(knowledge_query, observation_info.chat_history)
logger.info(f"[私聊][{self.private_name}]获取到知识: {knowledge[:100]}..., 来源: {source}")
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"[私聊][{self.private_name}]获取知识时出错: {str(fetch_err)}")
conversation_info.done_action[action_index].update(
{"status": "recall", "final_reason": f"获取知识失败: {str(fetch_err)}"}
)
self.conversation_info.last_successful_reply_action = None # 重置状态
# knowledge, source = await self.knowledge_fetcher.fetch(knowledge_query, observation_info.chat_history)
# logger.info(f"[私聊][{self.private_name}]获取到知识: {knowledge[:100]}..., 来源: {source}")
# 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"[私聊][{self.private_name}]获取知识时出错: {str(fetch_err)}")
# conversation_info.done_action[action_index].update(
# {"status": "recall", "final_reason": f"获取知识失败: {str(fetch_err)}"}
# )
# self.conversation_info.last_successful_reply_action = None # 重置状态
elif action == "rethink_goal":
self.state = ConversationState.RETHINKING

View File

@ -6,7 +6,6 @@ from ..chat.message import Message
from maim_message import UserInfo, Seg
from src.plugins.chat.message import MessageSending, MessageSet
from src.plugins.chat.message_sender import message_manager
from ..storage.storage import MessageStorage
from ...config.config import global_config
from rich.traceback import install
@ -21,7 +20,6 @@ class DirectMessageSender:
def __init__(self, private_name: str):
self.private_name = private_name
self.storage = MessageStorage()
async def send_message(
self,
@ -73,7 +71,6 @@ class DirectMessageSender:
message_set = MessageSet(chat_stream, message_id)
message_set.add_message(message)
await message_manager.add_message(message_set)
await self.storage.store_message(message, chat_stream)
logger.info(f"[私聊][{self.private_name}]PFC消息已发送: {content}")
except Exception as e:

View File

@ -1,3 +1,14 @@
# 用于访问记忆系统
from src.plugins.memory_system.Hippocampus import HippocampusManager
# --- NEW IMPORT ---
# 从 heartflow 导入知识检索和数据库查询函数/实例
from src.plugins.heartFC_chat.heartflow_prompt_builder import prompt_builder
# --- END NEW IMPORT ---
# 可能用于旧知识库提取主题 (如果需要回退到旧方法)
# import jieba # 如果报错说找不到 jieba可能需要安装: pip install jieba
# import re # 正则表达式库,通常 Python 自带
from typing import Tuple, List, Dict, Any
from src.common.logger import get_module_logger
from ..models.utils_model import LLMRequest
@ -18,17 +29,21 @@ PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊
当前对话目标{goals_str}
{knowledge_info_str}
你有以下这些知识
{retrieved_knowledge_str}
请你**记住上面的知识**在回复中有可能会用到
最近的聊天记录
{chat_history_text}
{retrieved_memory_str}
请根据上述信息结合聊天记录回复对方该回复应该
1. 符合对话目标""的角度发言不要自己与自己对话
2. 符合你的性格特征和身份细节
3. 通俗易懂自然流畅像正常聊天一样简短通常20字以内除非特殊情况
4. 可以适当利用相关知识不要生硬引用
4. 可以适当利用相关知识和回忆**不要生硬引用**若无必要也可以不利用
5. 自然得体结合聊天记录逻辑合理且没有重复表达同质内容
请注意把握聊天内容不要回复的太有条理可以有个性请分清""和对方说的话不要把""说的话当做对方说的话这是你自己说的话
@ -39,21 +54,24 @@ PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊
请直接输出回复内容不需要任何额外格式"""
# Prompt for send_new_message (追问/补充)
PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息:
PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息:
当前对话目标{goals_str}
{knowledge_info_str}
你有以下这些知识
{retrieved_knowledge_str}
请你**记住上面的知识**在发消息时有可能会用到
最近的聊天记录
{chat_history_text}
{retrieved_memory_str}
请根据上述信息结合聊天记录继续发一条新消息例如对之前消息的补充深入话题或追问等等该消息应该
请根据上述信息结合聊天记录继续发一条新消息例如对之前消息的补充深入话题或追问等等该消息应该
1. 符合对话目标""的角度发言不要自己与自己对话
2. 符合你的性格特征和身份细节
3. 通俗易懂自然流畅像正常聊天一样简短通常20字以内除非特殊情况
4. 可以适当利用相关知识不要生硬引用
4. 可以适当利用相关知识和回忆**不要生硬引用**若无必要也可以不利用
5. 跟之前你发的消息自然的衔接逻辑合理且没有重复表达同质内容或部分重叠内容
请注意把握聊天内容不用太有条理可以有个性请分清""和对方说的话不要把""说的话当做对方说的话这是你自己说的话
@ -98,6 +116,39 @@ class ReplyGenerator:
self.chat_observer = ChatObserver.get_instance(stream_id, private_name)
self.reply_checker = ReplyChecker(stream_id, private_name)
# _get_memory_info 保持不变,因为它不是与 heartflow 重复的部分
async def _get_memory_info(self, text: str) -> str:
"""根据文本自动检索相关记忆"""
memory_prompt = ""
related_memory_info = ""
try:
related_memory = await HippocampusManager.get_instance().get_memory_from_text(
text=text,
max_memory_num=2, # 最多获取 2 条记忆
max_memory_length=2, # 每条记忆长度限制(这个参数含义可能需确认)
max_depth=3, # 搜索深度
fast_retrieval=False, # 是否快速检索
)
if related_memory:
for memory in related_memory:
# memory[0] 是记忆ID, memory[1] 是记忆内容
related_memory_info += memory[1] + "\n" # 将记忆内容拼接起来
if related_memory_info:
memory_prompt = f"你回忆起:\n{related_memory_info.strip()}\n(以上是你的回忆,不一定是目前聊天里的人说的,回忆中别人说的事情也不一定是准确的,请记住)\n"
logger.debug(f"[私聊][{self.private_name}]自动检索到记忆: {related_memory_info.strip()[:100]}...")
else:
logger.debug(f"[私聊][{self.private_name}]自动检索记忆返回为空。")
else:
logger.debug(f"[私聊][{self.private_name}]未自动检索到相关记忆。")
except Exception as e:
logger.error(f"[私聊][{self.private_name}]自动检索记忆时出错: {e}")
# memory_prompt = "检索记忆时出错。\n" # 可以选择是否提示错误
return memory_prompt
# --- REMOVED _get_prompt_info_old ---
# --- REMOVED _get_prompt_info ---
# 修改 generate 方法签名,增加 action_type 参数
async def generate(
self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str
@ -137,38 +188,6 @@ class ReplyGenerator:
else:
goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况
# --- 新增:构建知识信息字符串 ---
knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考
try:
# 检查 conversation_info 是否有 knowledge_list 并且不为空
if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list:
# 最多只显示最近的 5 条知识
recent_knowledge = conversation_info.knowledge_list[-5:]
for i, knowledge_item in enumerate(recent_knowledge):
if isinstance(knowledge_item, dict):
query = knowledge_item.get("query", "未知查询")
knowledge = knowledge_item.get("knowledge", "无知识内容")
source = knowledge_item.get("source", "未知来源")
# 只取知识内容的前 2000 个字
knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 2000 else knowledge
knowledge_info_str += (
f"{i + 1}. 关于 '{query}' (来源: {source}): {knowledge_snippet}\n" # 格式微调,更简洁
)
else:
knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n"
if not recent_knowledge:
knowledge_info_str += "- 暂无。\n" # 更简洁的提示
else:
knowledge_info_str += "- 暂无。\n"
except AttributeError:
logger.warning(f"[私聊][{self.private_name}]ConversationInfo 对象可能缺少 knowledge_list 属性。")
knowledge_info_str += "- 获取知识列表时出错。\n"
except Exception as e:
logger.error(f"[私聊][{self.private_name}]构建知识信息字符串时出错: {e}")
knowledge_info_str += "- 处理知识列表时出错。\n"
# 获取聊天历史记录 (chat_history_text)
chat_history_text = observation_info.chat_history_str
if observation_info.new_messages_count > 0 and observation_info.unprocessed_messages:
@ -186,6 +205,42 @@ class ReplyGenerator:
# 构建 Persona 文本 (persona_text)
persona_text = f"你的名字是{self.name}{self.personality_info}"
retrieved_memory_str = ""
retrieved_knowledge_str = ""
# 使用 chat_history_text 作为检索的上下文,因为它包含了最近的对话和新消息
retrieval_context = chat_history_text
if retrieval_context and retrieval_context != "还没有聊天记录。" and retrieval_context != "[构建聊天记录出错]":
try:
# 提取记忆 (调用本地的 _get_memory_info)
logger.debug(f"[私聊][{self.private_name}]开始自动检索记忆...")
retrieved_memory_str = await self._get_memory_info(text=retrieval_context)
if retrieved_memory_str:
logger.info(f"[私聊][{self.private_name}]自动检索到记忆片段。")
else:
logger.info(f"[私聊][{self.private_name}]未自动检索到相关记忆。")
# --- MODIFIED KNOWLEDGE RETRIEVAL ---
# 提取知识 (调用导入的 prompt_builder.get_prompt_info)
logger.debug(f"[私聊][{self.private_name}]开始自动检索知识 (使用导入函数)...")
# 使用导入的 prompt_builder 实例及其方法
retrieved_knowledge_str = await prompt_builder.get_prompt_info(
message=retrieval_context, threshold=0.38
)
# --- END MODIFIED KNOWLEDGE RETRIEVAL ---
if retrieved_knowledge_str:
logger.info(f"[私聊][{self.private_name}]自动检索到相关知识。")
else:
logger.info(f"[私聊][{self.private_name}]未自动检索到相关知识。")
except Exception as retrieval_err:
logger.error(f"[私聊][{self.private_name}]在自动检索记忆/知识时发生错误: {retrieval_err}")
retrieved_memory_str = "检索记忆时出错。\n"
retrieved_knowledge_str = "检索知识时出错。\n"
else:
logger.debug(f"[私聊][{self.private_name}]聊天记录为空或无效,跳过自动记忆/知识检索。")
retrieved_memory_str = "无聊天记录,无法自动检索记忆。\n"
retrieved_knowledge_str = "无聊天记录,无法自动检索知识。\n"
# --- 选择 Prompt ---
if action_type == "send_new_message":
@ -203,7 +258,11 @@ class ReplyGenerator:
persona_text=persona_text,
goals_str=goals_str,
chat_history_text=chat_history_text,
knowledge_info_str=knowledge_info_str,
# knowledge_info_str=knowledge_info_str, # 移除了这个旧的知识展示方式
retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。", # 如果为空则提示无
retrieved_knowledge_str=retrieved_knowledge_str
if retrieved_knowledge_str
else "无相关知识。", # 如果为空则提示无
)
# --- 调用 LLM 生成 ---