feat:隔离所有记忆和问题
This commit is contained in:
@@ -104,6 +104,8 @@ class HeartFChatting:
|
||||
self.is_mute = False
|
||||
|
||||
self.last_active_time = time.time() # 记录上一次非noreply时间
|
||||
|
||||
self.questioned = False
|
||||
|
||||
|
||||
async def start(self):
|
||||
@@ -180,16 +182,27 @@ class HeartFChatting:
|
||||
filter_command=True,
|
||||
)
|
||||
|
||||
force_reply = False
|
||||
if time.time() - self.last_active_time > 300: #长久没有回复,可以试试主动发言
|
||||
print(f"{self.log_prefix} 长久没有回复,可以试试主动发言")
|
||||
if random.random() < 0.02: # 30%概率主动发言
|
||||
question_probability = 0
|
||||
if time.time() - self.last_active_time > 1200:
|
||||
question_probability = 0.04
|
||||
elif time.time() - self.last_active_time > 600:
|
||||
question_probability = 0.02
|
||||
elif time.time() - self.last_active_time > 300:
|
||||
question_probability = 0.005
|
||||
else:
|
||||
question_probability = 0.001
|
||||
|
||||
if question_probability > 0 and not self.questioned and len(global_conflict_tracker.get_questions_by_chat_id(self.stream_id)) > 0: #长久没有回复,可以试试主动发言,提问概率随着时间增加
|
||||
if random.random() < question_probability: # 30%概率主动发言
|
||||
print(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题")
|
||||
self.last_active_time = time.time()
|
||||
cycle_timers, thinking_id = self.start_cycle()
|
||||
question_maker = QuestionMaker(self.stream_id)
|
||||
question, conflict_context = await question_maker.make_question()
|
||||
await global_conflict_tracker.track_conflict(question, conflict_context, True, self.stream_id)
|
||||
await self._lift_question_reply(question,cycle_timers,thinking_id)
|
||||
if question and conflict_context:
|
||||
await global_conflict_tracker.track_conflict(question, conflict_context, True, self.stream_id)
|
||||
await self._lift_question_reply(question,cycle_timers,thinking_id)
|
||||
self.end_cycle(cycle_timers, thinking_id)
|
||||
|
||||
|
||||
if len(recent_messages_list) >= 1:
|
||||
@@ -703,6 +716,8 @@ class HeartFChatting:
|
||||
|
||||
elif action_planner_info.action_type == "reply":
|
||||
# 直接当场执行reply逻辑
|
||||
self.questioned = False
|
||||
# 刷新主动发言状态
|
||||
|
||||
reason = action_planner_info.reasoning or "选择回复"
|
||||
await database_api.store_action_info(
|
||||
|
||||
@@ -324,6 +324,7 @@ class MemoryChest(BaseModel):
|
||||
|
||||
title = TextField() # 标题
|
||||
content = TextField() # 内容
|
||||
chat_id = TextField(null=True) # 聊天ID
|
||||
locked = BooleanField(default=False) # 是否锁定
|
||||
|
||||
class Meta:
|
||||
@@ -339,6 +340,7 @@ class MemoryConflict(BaseModel):
|
||||
create_time = FloatField() # 创建时间
|
||||
update_time = FloatField() # 更新时间
|
||||
context = TextField(null=True) # 上下文
|
||||
chat_id = TextField(null=True) # 聊天ID
|
||||
|
||||
class Meta:
|
||||
table_name = "memory_conflicts"
|
||||
|
||||
@@ -13,10 +13,12 @@ from src.plugin_system.apis.message_api import build_readable_messages
|
||||
from src.plugin_system.apis.message_api import get_raw_msg_by_timestamp_with_chat
|
||||
from json_repair import repair_json
|
||||
from src.memory_system.questions import global_conflict_tracker
|
||||
|
||||
from .memory_utils import (
|
||||
find_best_matching_memory,
|
||||
check_title_exists_fuzzy,
|
||||
get_all_titles,
|
||||
get_memory_titles_by_chat_id_weighted,
|
||||
|
||||
)
|
||||
|
||||
@@ -306,7 +308,6 @@ class MemoryChest:
|
||||
title, (reasoning_content, model_name, tool_calls) = await self.LLMRequest.generate_response_async(prompt)
|
||||
|
||||
# 根据 title 获取 titles 里的对应项
|
||||
titles = get_all_titles()
|
||||
selected_title = None
|
||||
|
||||
# 使用模糊查找匹配标题
|
||||
@@ -379,7 +380,8 @@ class MemoryChest:
|
||||
# 保存到数据库
|
||||
MemoryChestModel.create(
|
||||
title=title.strip(),
|
||||
content=content
|
||||
content=content,
|
||||
chat_id=chat_id
|
||||
)
|
||||
logger.info(f"已保存记忆仓库内容,标题: {title.strip()}, chat_id: {chat_id}")
|
||||
|
||||
@@ -393,24 +395,26 @@ class MemoryChest:
|
||||
except Exception as e:
|
||||
logger.error(f"保存记忆仓库内容时出错: {e}")
|
||||
|
||||
async def choose_merge_target(self, memory_title: str) -> list[str]:
|
||||
async def choose_merge_target(self, memory_title: str, chat_id: str = None) -> list[str]:
|
||||
"""
|
||||
选择与给定记忆标题相关的记忆目标
|
||||
|
||||
Args:
|
||||
memory_title: 要匹配的记忆标题
|
||||
chat_id: 聊天ID,用于加权抽样
|
||||
|
||||
Returns:
|
||||
list[str]: 选中的记忆内容列表
|
||||
"""
|
||||
try:
|
||||
all_titles = get_all_titles(exclude_locked=True)
|
||||
# 如果提供了chat_id,使用加权抽样
|
||||
all_titles = get_memory_titles_by_chat_id_weighted(chat_id)
|
||||
# 剔除掉输入的 memory_title 本身
|
||||
all_titles = [title for title in all_titles if title and title.strip() != (memory_title or "").strip()]
|
||||
|
||||
content = ""
|
||||
display_index = 1
|
||||
for title in all_titles:
|
||||
# 剔除掉输入的 memory_title 本身
|
||||
if title and title.strip() == (memory_title or "").strip():
|
||||
continue
|
||||
content += f"{display_index}. {title}\n"
|
||||
display_index += 1
|
||||
|
||||
@@ -615,7 +619,7 @@ class MemoryChest:
|
||||
logger.error(f"解析合并目标JSON时出错: {e}")
|
||||
return []
|
||||
|
||||
async def merge_memory(self,memory_list: list[str]) -> tuple[str, str]:
|
||||
async def merge_memory(self,memory_list: list[str], chat_id: str = None) -> tuple[str, str]:
|
||||
"""
|
||||
合并记忆
|
||||
"""
|
||||
@@ -670,7 +674,7 @@ class MemoryChest:
|
||||
if part2_content and part2_content.strip() != "none":
|
||||
logger.info(f"合并记忆part2记录冲突内容: {len(part2_content)} 字符")
|
||||
# 记录冲突到数据库
|
||||
await global_conflict_tracker.record_memory_merge_conflict(part2_content)
|
||||
await global_conflict_tracker.record_memory_merge_conflict(part2_content,chat_id)
|
||||
|
||||
# 处理part1:生成标题并保存
|
||||
if part1_content and part1_content.strip() != "none":
|
||||
@@ -679,7 +683,8 @@ class MemoryChest:
|
||||
# 保存part1到数据库
|
||||
MemoryChestModel.create(
|
||||
title=merged_title,
|
||||
content=part1_content
|
||||
content=part1_content,
|
||||
chat_id=chat_id
|
||||
)
|
||||
|
||||
logger.info(f"合并记忆part1已保存: {merged_title}")
|
||||
|
||||
@@ -97,14 +97,14 @@ class MemoryManagementTask(AsyncTask):
|
||||
if current_count < 10:
|
||||
return
|
||||
|
||||
# 随机选择一个记忆标题
|
||||
selected_title = self._get_random_memory_title()
|
||||
# 随机选择一个记忆标题和chat_id
|
||||
selected_title, selected_chat_id = self._get_random_memory_title()
|
||||
if not selected_title:
|
||||
logger.warning("无法获取随机记忆标题,跳过执行")
|
||||
return
|
||||
|
||||
# 执行choose_merge_target获取相关记忆(标题与内容)
|
||||
related_titles, related_contents = await global_memory_chest.choose_merge_target(selected_title)
|
||||
related_titles, related_contents = await global_memory_chest.choose_merge_target(selected_title, selected_chat_id)
|
||||
if not related_titles or not related_contents:
|
||||
logger.info("无合适合并内容,跳过本次合并")
|
||||
return
|
||||
@@ -127,21 +127,21 @@ class MemoryManagementTask(AsyncTask):
|
||||
except Exception as e:
|
||||
logger.error(f"[记忆管理] 执行记忆管理任务时发生错误: {e}", exc_info=True)
|
||||
|
||||
def _get_random_memory_title(self) -> str:
|
||||
"""随机获取一个记忆标题"""
|
||||
def _get_random_memory_title(self) -> tuple[str, str]:
|
||||
"""随机获取一个记忆标题和对应的chat_id"""
|
||||
try:
|
||||
# 获取所有记忆标题
|
||||
all_titles = get_all_titles()
|
||||
if not all_titles:
|
||||
return ""
|
||||
# 获取所有记忆记录
|
||||
all_memories = MemoryChestModel.select()
|
||||
if not all_memories:
|
||||
return "", ""
|
||||
|
||||
# 随机选择一个标题
|
||||
selected_title = random.choice(all_titles)
|
||||
return selected_title
|
||||
# 随机选择一个记忆
|
||||
selected_memory = random.choice(list(all_memories))
|
||||
return selected_memory.title, selected_memory.chat_id or ""
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[记忆管理] 获取随机记忆标题时发生错误: {e}")
|
||||
return ""
|
||||
return "", ""
|
||||
|
||||
def _delete_original_memories(self, related_titles: List[str]) -> int:
|
||||
"""按标题删除原始记忆"""
|
||||
|
||||
@@ -218,3 +218,89 @@ def check_title_exists_fuzzy(target_title: str, similarity_threshold: float = 0.
|
||||
except Exception as e:
|
||||
logger.error(f"检查标题是否存在时出错: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_memories_by_chat_id_weighted(target_chat_id: str, same_chat_weight: float = 0.95, other_chat_weight: float = 0.05) -> List[Tuple[str, str, str]]:
|
||||
"""
|
||||
根据chat_id进行加权抽样获取记忆列表
|
||||
|
||||
Args:
|
||||
target_chat_id: 目标聊天ID
|
||||
same_chat_weight: 同chat_id记忆的权重,默认0.95(95%概率)
|
||||
other_chat_weight: 其他chat_id记忆的权重,默认0.05(5%概率)
|
||||
|
||||
Returns:
|
||||
List[Tuple[str, str, str]]: 选中的记忆列表,每个元素为(title, content, chat_id)
|
||||
"""
|
||||
try:
|
||||
# 获取所有记忆
|
||||
all_memories = MemoryChestModel.select()
|
||||
|
||||
# 按chat_id分组
|
||||
same_chat_memories = []
|
||||
other_chat_memories = []
|
||||
|
||||
for memory in all_memories:
|
||||
if memory.title and not memory.locked: # 排除锁定的记忆
|
||||
if memory.chat_id == target_chat_id:
|
||||
same_chat_memories.append((memory.title, memory.content, memory.chat_id))
|
||||
else:
|
||||
other_chat_memories.append((memory.title, memory.content, memory.chat_id))
|
||||
|
||||
# 如果没有同chat_id的记忆,返回空列表
|
||||
if not same_chat_memories:
|
||||
logger.warning(f"未找到chat_id为 '{target_chat_id}' 的记忆")
|
||||
return []
|
||||
|
||||
# 计算抽样数量
|
||||
total_same = len(same_chat_memories)
|
||||
total_other = len(other_chat_memories)
|
||||
|
||||
# 根据权重计算抽样数量
|
||||
if total_other > 0:
|
||||
# 计算其他chat_id记忆的抽样数量(至少1个,最多不超过总数的10%)
|
||||
other_sample_count = max(1, min(total_other, int(total_same * other_chat_weight / same_chat_weight)))
|
||||
else:
|
||||
other_sample_count = 0
|
||||
|
||||
# 随机抽样
|
||||
selected_memories = []
|
||||
|
||||
# 选择同chat_id的记忆(全部选择,因为权重很高)
|
||||
selected_memories.extend(same_chat_memories)
|
||||
|
||||
# 随机选择其他chat_id的记忆
|
||||
if other_sample_count > 0 and total_other > 0:
|
||||
import random
|
||||
other_selected = random.sample(other_chat_memories, min(other_sample_count, total_other))
|
||||
selected_memories.extend(other_selected)
|
||||
|
||||
logger.info(f"加权抽样结果: 同chat_id记忆 {len(same_chat_memories)} 条,其他chat_id记忆 {min(other_sample_count, total_other)} 条")
|
||||
|
||||
return selected_memories
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"按chat_id加权抽样记忆时出错: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def get_memory_titles_by_chat_id_weighted(target_chat_id: str, same_chat_weight: float = 0.95, other_chat_weight: float = 0.05) -> List[str]:
|
||||
"""
|
||||
根据chat_id进行加权抽样获取记忆标题列表(用于合并选择)
|
||||
|
||||
Args:
|
||||
target_chat_id: 目标聊天ID
|
||||
same_chat_weight: 同chat_id记忆的权重,默认0.95(95%概率)
|
||||
other_chat_weight: 其他chat_id记忆的权重,默认0.05(5%概率)
|
||||
|
||||
Returns:
|
||||
List[str]: 选中的记忆标题列表
|
||||
"""
|
||||
try:
|
||||
memories = get_memories_by_chat_id_weighted(target_chat_id, same_chat_weight, other_chat_weight)
|
||||
titles = [memory[0] for memory in memories] # 提取标题
|
||||
return titles
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"按chat_id加权抽样记忆标题时出错: {e}")
|
||||
return []
|
||||
@@ -26,7 +26,7 @@ class QuestionMaker:
|
||||
|
||||
|
||||
async def get_all_conflicts(self):
|
||||
conflicts = list(MemoryConflict.select())
|
||||
conflicts = list(MemoryConflict.select().where(MemoryConflict.chat_id == self.chat_id))
|
||||
return conflicts
|
||||
|
||||
async def get_un_answered_conflict(self):
|
||||
@@ -35,10 +35,14 @@ class QuestionMaker:
|
||||
|
||||
async def get_random_unanswered_conflict(self):
|
||||
conflicts = await self.get_un_answered_conflict()
|
||||
if not conflicts:
|
||||
return None
|
||||
return random.choice(conflicts)
|
||||
|
||||
async def make_question(self):
|
||||
conflict = await self.get_random_unanswered_conflict()
|
||||
if not conflict:
|
||||
return None, None
|
||||
question = conflict.conflict_content
|
||||
conflict_context = conflict.context
|
||||
chat_context = self.get_context()
|
||||
|
||||
@@ -165,6 +165,7 @@ class ConflictTracker:
|
||||
create_time=time.time(),
|
||||
update_time=time.time(),
|
||||
answer="",
|
||||
chat_id=chat_id,
|
||||
)
|
||||
|
||||
logger.info(f"记录冲突内容: {len(conflict_content)} 字符")
|
||||
@@ -278,6 +279,7 @@ class ConflictTracker:
|
||||
create_time=time.time(),
|
||||
update_time=time.time(),
|
||||
answer="",
|
||||
chat_id=tracker.chat_id,
|
||||
)
|
||||
logger.info(f"记录冲突内容(未解答): {len(original_question)} 字符")
|
||||
logger.info(f"问题跟踪结束:{original_question}")
|
||||
@@ -304,7 +306,15 @@ class ConflictTracker:
|
||||
except Exception as e:
|
||||
logger.error(f"移除追踪器时出错: {e}")
|
||||
|
||||
async def add_or_update_conflict(self,conflict_content: str,create_time: float,update_time: float,answer: str = "",context: str = "") -> bool:
|
||||
async def add_or_update_conflict(
|
||||
self,
|
||||
conflict_content: str,
|
||||
create_time: float,
|
||||
update_time: float,
|
||||
answer: str = "",
|
||||
context: str = "",
|
||||
chat_id: str = None
|
||||
) -> bool:
|
||||
"""
|
||||
根据conflict_content匹配数据库内容,如果找到相同的就更新update_time和answer,
|
||||
如果没有相同的,就新建一条保存全部内容
|
||||
@@ -312,7 +322,8 @@ class ConflictTracker:
|
||||
try:
|
||||
# 尝试根据conflict_content查找现有记录
|
||||
existing_conflict = MemoryConflict.get_or_none(
|
||||
MemoryConflict.conflict_content == conflict_content
|
||||
MemoryConflict.conflict_content == conflict_content,
|
||||
MemoryConflict.chat_id == chat_id
|
||||
)
|
||||
|
||||
if existing_conflict:
|
||||
@@ -329,6 +340,7 @@ class ConflictTracker:
|
||||
update_time=update_time,
|
||||
answer=answer,
|
||||
context=context,
|
||||
chat_id=chat_id,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -336,7 +348,7 @@ class ConflictTracker:
|
||||
logger.error(f"添加或更新冲突记录时出错: {e}")
|
||||
return False
|
||||
|
||||
async def record_memory_merge_conflict(self, part2_content: str) -> bool:
|
||||
async def record_memory_merge_conflict(self, part2_content: str, chat_id: str = None) -> bool:
|
||||
"""
|
||||
记录记忆整合过程中的冲突内容(part2)
|
||||
|
||||
@@ -386,6 +398,7 @@ class ConflictTracker:
|
||||
conflict_content=question["question"],
|
||||
context=reasoning_content,
|
||||
start_following=False,
|
||||
chat_id=chat_id,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class CuriousAction(BaseAction):
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""执行频率调节动作"""
|
||||
try:
|
||||
if len(global_conflict_tracker.question_tracker_list) > 3:
|
||||
if len(global_conflict_tracker.question_tracker_list) > 1:
|
||||
return False, "当前有太多问题,请先解答完再提问,不要再使用make_question动作"
|
||||
|
||||
question = self.action_data.get("question", "")
|
||||
|
||||
Reference in New Issue
Block a user