mirror of https://github.com/Mai-with-u/MaiBot.git
附属函数参数修改
parent
e1a21c5a45
commit
e8922672aa
|
|
@ -9,7 +9,6 @@ import networkx as nx
|
|||
import numpy as np
|
||||
from typing import List, Tuple, Set, Coroutine, Any, Dict
|
||||
from collections import Counter
|
||||
from itertools import combinations
|
||||
import traceback
|
||||
|
||||
from rich.traceback import install
|
||||
|
|
@ -23,6 +22,8 @@ from src.chat.utils.chat_message_builder import (
|
|||
build_readable_messages,
|
||||
get_raw_msg_by_timestamp_with_chat_inclusive,
|
||||
) # 导入 build_readable_messages
|
||||
|
||||
|
||||
# 添加cosine_similarity函数
|
||||
def cosine_similarity(v1, v2):
|
||||
"""计算余弦相似度"""
|
||||
|
|
@ -51,18 +52,9 @@ def calculate_information_content(text):
|
|||
return entropy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
logger = get_logger("memory")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MemoryGraph:
|
||||
def __init__(self):
|
||||
self.G = nx.Graph() # 使用 networkx 的图结构
|
||||
|
|
@ -96,7 +88,7 @@ class MemoryGraph:
|
|||
if "memory_items" in self.G.nodes[concept]:
|
||||
# 获取现有的记忆项(已经是str格式)
|
||||
existing_memory = self.G.nodes[concept]["memory_items"]
|
||||
|
||||
|
||||
# 如果现有记忆不为空,则使用LLM整合新旧记忆
|
||||
if existing_memory and hippocampus_instance and hippocampus_instance.model_small:
|
||||
try:
|
||||
|
|
@ -170,16 +162,16 @@ class MemoryGraph:
|
|||
second_layer_items.append(memory_items)
|
||||
|
||||
return first_layer_items, second_layer_items
|
||||
|
||||
|
||||
async def _integrate_memories_with_llm(self, existing_memory: str, new_memory: str, llm_model: LLMRequest) -> str:
|
||||
"""
|
||||
使用LLM整合新旧记忆内容
|
||||
|
||||
|
||||
Args:
|
||||
existing_memory: 现有的记忆内容(字符串格式,可能包含多条记忆)
|
||||
new_memory: 新的记忆内容
|
||||
llm_model: LLM模型实例
|
||||
|
||||
|
||||
Returns:
|
||||
str: 整合后的记忆内容
|
||||
"""
|
||||
|
|
@ -203,8 +195,10 @@ class MemoryGraph:
|
|||
整合后的记忆:"""
|
||||
|
||||
# 调用LLM进行整合
|
||||
content, (reasoning_content, model_name, tool_calls) = await llm_model.generate_response_async(integration_prompt)
|
||||
|
||||
content, (reasoning_content, model_name, tool_calls) = await llm_model.generate_response_async(
|
||||
integration_prompt
|
||||
)
|
||||
|
||||
if content and content.strip():
|
||||
integrated_content = content.strip()
|
||||
logger.debug(f"LLM记忆整合成功,模型: {model_name}")
|
||||
|
|
@ -212,7 +206,7 @@ class MemoryGraph:
|
|||
else:
|
||||
logger.warning("LLM返回的整合结果为空,使用默认连接方式")
|
||||
return f"{existing_memory} | {new_memory}"
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"LLM记忆整合过程中出错: {e}")
|
||||
return f"{existing_memory} | {new_memory}"
|
||||
|
|
@ -238,7 +232,11 @@ class MemoryGraph:
|
|||
if memory_items:
|
||||
# 删除整个节点
|
||||
self.G.remove_node(topic)
|
||||
return f"删除了节点 {topic} 的完整记忆: {memory_items[:50]}..." if len(memory_items) > 50 else f"删除了节点 {topic} 的完整记忆: {memory_items}"
|
||||
return (
|
||||
f"删除了节点 {topic} 的完整记忆: {memory_items[:50]}..."
|
||||
if len(memory_items) > 50
|
||||
else f"删除了节点 {topic} 的完整记忆: {memory_items}"
|
||||
)
|
||||
else:
|
||||
# 如果没有记忆项,删除该节点
|
||||
self.G.remove_node(topic)
|
||||
|
|
@ -263,38 +261,40 @@ class Hippocampus:
|
|||
self.parahippocampal_gyrus = ParahippocampalGyrus(self)
|
||||
# 从数据库加载记忆图
|
||||
self.entorhinal_cortex.sync_memory_from_db()
|
||||
self.model_small = LLMRequest(model_set=model_config.model_task_config.utils_small, request_type="memory.modify")
|
||||
self.model_small = LLMRequest(
|
||||
model_set=model_config.model_task_config.utils_small, request_type="memory.modify"
|
||||
)
|
||||
|
||||
def get_all_node_names(self) -> list:
|
||||
"""获取记忆图中所有节点的名字列表"""
|
||||
return list(self.memory_graph.G.nodes())
|
||||
|
||||
|
||||
def calculate_weighted_activation(self, current_activation: float, edge_strength: int, target_node: str) -> float:
|
||||
"""
|
||||
计算考虑节点权重的激活值
|
||||
|
||||
|
||||
Args:
|
||||
current_activation: 当前激活值
|
||||
edge_strength: 边的强度
|
||||
target_node: 目标节点名称
|
||||
|
||||
|
||||
Returns:
|
||||
float: 计算后的激活值
|
||||
"""
|
||||
# 基础激活值计算
|
||||
base_activation = current_activation - (1 / edge_strength)
|
||||
|
||||
|
||||
if base_activation <= 0:
|
||||
return 0.0
|
||||
|
||||
|
||||
# 获取目标节点的权重
|
||||
if target_node in self.memory_graph.G:
|
||||
node_data = self.memory_graph.G.nodes[target_node]
|
||||
node_weight = node_data.get("weight", 1.0)
|
||||
|
||||
|
||||
# 权重加成:每次整合增加10%激活值,最大加成200%
|
||||
weight_multiplier = 1.0 + min((node_weight - 1.0) * 0.1, 2.0)
|
||||
|
||||
|
||||
return base_activation * weight_multiplier
|
||||
else:
|
||||
return base_activation
|
||||
|
|
@ -332,9 +332,7 @@ class Hippocampus:
|
|||
f"将主题用逗号隔开,并加上<>,例如<主题1>,<主题2>......尽可能精简。只需要列举最多{topic_num}个话题就好,不要有序号,不要告诉我其他内容。"
|
||||
f"如果确定找不出主题或者没有明显主题,返回<none>。"
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
return prompt
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -418,16 +416,13 @@ class Hippocampus:
|
|||
# 使用LLM提取关键词 - 根据详细文本长度分布优化topic_num计算
|
||||
text_length = len(text)
|
||||
topic_num: int | list[int] = 0
|
||||
|
||||
|
||||
|
||||
words = jieba.cut(text)
|
||||
keywords_lite = [word for word in words if len(word) > 1]
|
||||
keywords_lite = list(set(keywords_lite))
|
||||
if keywords_lite:
|
||||
logger.debug(f"提取关键词极简版: {keywords_lite}")
|
||||
|
||||
|
||||
|
||||
if text_length <= 12:
|
||||
topic_num = [1, 3] # 6-10字符: 1个关键词 (27.18%的文本)
|
||||
elif text_length <= 20:
|
||||
|
|
@ -455,7 +450,7 @@ class Hippocampus:
|
|||
if keywords:
|
||||
logger.debug(f"提取关键词: {keywords}")
|
||||
|
||||
return keywords,keywords_lite
|
||||
return keywords, keywords_lite
|
||||
|
||||
async def get_memory_from_topic(
|
||||
self,
|
||||
|
|
@ -570,20 +565,17 @@ class Hippocampus:
|
|||
for node, activation in remember_map.items():
|
||||
logger.debug(f"处理节点 '{node}' (激活值: {activation:.2f}):")
|
||||
node_data = self.memory_graph.G.nodes[node]
|
||||
memory_items = node_data.get("memory_items", "")
|
||||
# 直接使用完整的记忆内容
|
||||
if memory_items:
|
||||
if memory_items := node_data.get("memory_items", ""):
|
||||
logger.debug("节点包含完整记忆")
|
||||
# 计算记忆与关键词的相似度
|
||||
memory_words = set(jieba.cut(memory_items))
|
||||
text_words = set(keywords)
|
||||
all_words = memory_words | text_words
|
||||
if all_words:
|
||||
if all_words := memory_words | text_words:
|
||||
# 计算相似度(虽然这里没有使用,但保持逻辑一致性)
|
||||
v1 = [1 if word in memory_words else 0 for word in all_words]
|
||||
v2 = [1 if word in text_words else 0 for word in all_words]
|
||||
_ = cosine_similarity(v1, v2) # 计算但不使用,用_表示
|
||||
|
||||
|
||||
# 添加完整记忆到结果中
|
||||
all_memories.append((node, memory_items, activation))
|
||||
else:
|
||||
|
|
@ -613,7 +605,9 @@ class Hippocampus:
|
|||
|
||||
return result
|
||||
|
||||
async def get_activate_from_text(self, text: str, max_depth: int = 3, fast_retrieval: bool = False) -> tuple[float, list[str],list[str]]:
|
||||
async def get_activate_from_text(
|
||||
self, text: str, max_depth: int = 3, fast_retrieval: bool = False
|
||||
) -> tuple[float, list[str], list[str]]:
|
||||
"""从文本中提取关键词并获取相关记忆。
|
||||
|
||||
Args:
|
||||
|
|
@ -627,13 +621,13 @@ class Hippocampus:
|
|||
float: 激活节点数与总节点数的比值
|
||||
list[str]: 有效的关键词
|
||||
"""
|
||||
keywords,keywords_lite = await self.get_keywords_from_text(text)
|
||||
keywords, keywords_lite = await self.get_keywords_from_text(text)
|
||||
|
||||
# 过滤掉不存在于记忆图中的关键词
|
||||
valid_keywords = [keyword for keyword in keywords if keyword in self.memory_graph.G]
|
||||
if not valid_keywords:
|
||||
# logger.info("没有找到有效的关键词节点")
|
||||
return 0, keywords,keywords_lite
|
||||
return 0, keywords, keywords_lite
|
||||
|
||||
logger.debug(f"有效的关键词: {', '.join(valid_keywords)}")
|
||||
|
||||
|
|
@ -700,7 +694,7 @@ class Hippocampus:
|
|||
activation_ratio = activation_ratio * 50
|
||||
logger.debug(f"总激活值: {total_activation:.2f}, 总节点数: {total_nodes}, 激活: {activation_ratio}")
|
||||
|
||||
return activation_ratio, keywords,keywords_lite
|
||||
return activation_ratio, keywords, keywords_lite
|
||||
|
||||
|
||||
# 负责海马体与其他部分的交互
|
||||
|
|
@ -730,7 +724,7 @@ class EntorhinalCortex:
|
|||
continue
|
||||
|
||||
memory_items = data.get("memory_items", "")
|
||||
|
||||
|
||||
# 直接检查字符串是否为空,不需要分割成列表
|
||||
if not memory_items or memory_items.strip() == "":
|
||||
self.memory_graph.G.remove_node(concept)
|
||||
|
|
@ -865,7 +859,9 @@ class EntorhinalCortex:
|
|||
|
||||
end_time = time.time()
|
||||
logger.info(f"[数据库] 同步完成,总耗时: {end_time - start_time:.2f}秒")
|
||||
logger.info(f"[数据库] 同步了 {len(nodes_to_create) + len(nodes_to_update)} 个节点和 {len(edges_to_create) + len(edges_to_update)} 条边")
|
||||
logger.info(
|
||||
f"[数据库] 同步了 {len(nodes_to_create) + len(nodes_to_update)} 个节点和 {len(edges_to_create) + len(edges_to_update)} 条边"
|
||||
)
|
||||
|
||||
async def resync_memory_to_db(self):
|
||||
"""清空数据库并重新同步所有记忆数据"""
|
||||
|
|
@ -888,7 +884,7 @@ class EntorhinalCortex:
|
|||
nodes_data = []
|
||||
for concept, data in memory_nodes:
|
||||
memory_items = data.get("memory_items", "")
|
||||
|
||||
|
||||
# 直接检查字符串是否为空,不需要分割成列表
|
||||
if not memory_items or memory_items.strip() == "":
|
||||
self.memory_graph.G.remove_node(concept)
|
||||
|
|
@ -960,7 +956,7 @@ class EntorhinalCortex:
|
|||
|
||||
# 清空当前图
|
||||
self.memory_graph.G.clear()
|
||||
|
||||
|
||||
# 统计加载情况
|
||||
total_nodes = 0
|
||||
loaded_nodes = 0
|
||||
|
|
@ -969,7 +965,7 @@ class EntorhinalCortex:
|
|||
# 从数据库加载所有节点
|
||||
nodes = list(GraphNodes.select())
|
||||
total_nodes = len(nodes)
|
||||
|
||||
|
||||
for node in nodes:
|
||||
concept = node.concept
|
||||
try:
|
||||
|
|
@ -978,7 +974,7 @@ class EntorhinalCortex:
|
|||
logger.warning(f"节点 {concept} 的memory_items为空,跳过")
|
||||
skipped_nodes += 1
|
||||
continue
|
||||
|
||||
|
||||
# 直接使用memory_items
|
||||
memory_items = node.memory_items.strip()
|
||||
|
||||
|
|
@ -999,11 +995,15 @@ class EntorhinalCortex:
|
|||
last_modified = node.last_modified or current_time
|
||||
|
||||
# 获取权重属性
|
||||
weight = node.weight if hasattr(node, 'weight') and node.weight is not None else 1.0
|
||||
|
||||
weight = node.weight if hasattr(node, "weight") and node.weight is not None else 1.0
|
||||
|
||||
# 添加节点到图中
|
||||
self.memory_graph.G.add_node(
|
||||
concept, memory_items=memory_items, weight=weight, created_time=created_time, last_modified=last_modified
|
||||
concept,
|
||||
memory_items=memory_items,
|
||||
weight=weight,
|
||||
created_time=created_time,
|
||||
last_modified=last_modified,
|
||||
)
|
||||
loaded_nodes += 1
|
||||
except Exception as e:
|
||||
|
|
@ -1044,9 +1044,11 @@ class EntorhinalCortex:
|
|||
|
||||
if need_update:
|
||||
logger.info("[数据库] 已为缺失的时间字段进行补充")
|
||||
|
||||
|
||||
# 输出加载统计信息
|
||||
logger.info(f"[数据库] 记忆加载完成: 总计 {total_nodes} 个节点, 成功加载 {loaded_nodes} 个, 跳过 {skipped_nodes} 个")
|
||||
logger.info(
|
||||
f"[数据库] 记忆加载完成: 总计 {total_nodes} 个节点, 成功加载 {loaded_nodes} 个, 跳过 {skipped_nodes} 个"
|
||||
)
|
||||
|
||||
|
||||
# 负责整合,遗忘,合并记忆
|
||||
|
|
@ -1054,10 +1056,12 @@ class ParahippocampalGyrus:
|
|||
def __init__(self, hippocampus: Hippocampus):
|
||||
self.hippocampus = hippocampus
|
||||
self.memory_graph = hippocampus.memory_graph
|
||||
|
||||
self.memory_modify_model = LLMRequest(model_set=model_config.model_task_config.utils, request_type="memory.modify")
|
||||
|
||||
async def memory_compress(self, messages: list, compress_rate=0.1):
|
||||
self.memory_modify_model = LLMRequest(
|
||||
model_set=model_config.model_task_config.utils, request_type="memory.modify"
|
||||
)
|
||||
|
||||
async def memory_compress(self, messages: list[DatabaseMessages], compress_rate=0.1):
|
||||
"""压缩和总结消息内容,生成记忆主题和摘要。
|
||||
|
||||
Args:
|
||||
|
|
@ -1162,7 +1166,7 @@ class ParahippocampalGyrus:
|
|||
similar_topics.sort(key=lambda x: x[1], reverse=True)
|
||||
similar_topics = similar_topics[:3]
|
||||
similar_topics_dict[topic] = similar_topics
|
||||
|
||||
|
||||
if global_config.debug.show_prompt:
|
||||
logger.info(f"prompt: {topic_what_prompt}")
|
||||
logger.info(f"压缩后的记忆: {compressed_memory}")
|
||||
|
|
@ -1258,14 +1262,14 @@ class ParahippocampalGyrus:
|
|||
# --- 如果节点不为空,则执行原来的不活跃检查和随机移除逻辑 ---
|
||||
last_modified = node_data.get("last_modified", current_time)
|
||||
node_weight = node_data.get("weight", 1.0)
|
||||
|
||||
|
||||
# 条件1:检查是否长时间未修改 (使用配置的遗忘时间)
|
||||
time_threshold = 3600 * global_config.memory.memory_forget_time
|
||||
|
||||
|
||||
# 基于权重调整遗忘阈值:权重越高,需要更长时间才能被遗忘
|
||||
# 权重为1时使用默认阈值,权重越高阈值越大(越难遗忘)
|
||||
adjusted_threshold = time_threshold * node_weight
|
||||
|
||||
|
||||
if current_time - last_modified > adjusted_threshold and memory_items:
|
||||
# 既然每个节点现在是完整记忆,直接删除整个节点
|
||||
try:
|
||||
|
|
@ -1314,8 +1318,6 @@ class ParahippocampalGyrus:
|
|||
logger.info(f"[遗忘] 总耗时: {end_time - start_time:.2f}秒")
|
||||
|
||||
|
||||
|
||||
|
||||
class HippocampusManager:
|
||||
def __init__(self):
|
||||
self._hippocampus: Hippocampus = None # type: ignore
|
||||
|
|
@ -1360,29 +1362,32 @@ class HippocampusManager:
|
|||
"""为指定chat_id构建记忆(在heartFC_chat.py中调用)"""
|
||||
if not self._initialized:
|
||||
raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法")
|
||||
|
||||
|
||||
try:
|
||||
# 检查是否需要构建记忆
|
||||
logger.info(f"为 {chat_id} 构建记忆")
|
||||
if memory_segment_manager.check_and_build_memory_for_chat(chat_id):
|
||||
logger.info(f"为 {chat_id} 构建记忆,需要构建记忆")
|
||||
messages = memory_segment_manager.get_messages_for_memory_build(chat_id, 50)
|
||||
|
||||
|
||||
build_probability = 0.3 * global_config.memory.memory_build_frequency
|
||||
|
||||
|
||||
if messages and random.random() < build_probability:
|
||||
logger.info(f"为 {chat_id} 构建记忆,消息数量: {len(messages)}")
|
||||
|
||||
|
||||
# 调用记忆压缩和构建
|
||||
compressed_memory, similar_topics_dict = await self._hippocampus.parahippocampal_gyrus.memory_compress(
|
||||
(
|
||||
compressed_memory,
|
||||
similar_topics_dict,
|
||||
) = await self._hippocampus.parahippocampal_gyrus.memory_compress(
|
||||
messages, global_config.memory.memory_compress_rate
|
||||
)
|
||||
|
||||
|
||||
# 添加记忆节点
|
||||
current_time = time.time()
|
||||
for topic, memory in compressed_memory:
|
||||
await self._hippocampus.memory_graph.add_dot(topic, memory, self._hippocampus)
|
||||
|
||||
|
||||
# 连接相似主题
|
||||
if topic in similar_topics_dict:
|
||||
similar_topics = similar_topics_dict[topic]
|
||||
|
|
@ -1390,23 +1395,23 @@ class HippocampusManager:
|
|||
if topic != similar_topic:
|
||||
strength = int(similarity * 10)
|
||||
self._hippocampus.memory_graph.G.add_edge(
|
||||
topic, similar_topic,
|
||||
topic,
|
||||
similar_topic,
|
||||
strength=strength,
|
||||
created_time=current_time,
|
||||
last_modified=current_time
|
||||
last_modified=current_time,
|
||||
)
|
||||
|
||||
|
||||
# 同步到数据库
|
||||
await self._hippocampus.entorhinal_cortex.sync_memory_to_db()
|
||||
logger.info(f"为 {chat_id} 构建记忆完成")
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"为 {chat_id} 构建记忆失败: {e}")
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
async def get_memory_from_topic(
|
||||
self, valid_keywords: list[str], max_memory_num: int = 3, max_memory_length: int = 2, max_depth: int = 3
|
||||
|
|
@ -1423,16 +1428,20 @@ class HippocampusManager:
|
|||
response = []
|
||||
return response
|
||||
|
||||
async def get_activate_from_text(self, text: str, max_depth: int = 3, fast_retrieval: bool = False) -> tuple[float, list[str]]:
|
||||
async def get_activate_from_text(
|
||||
self, text: str, max_depth: int = 3, fast_retrieval: bool = False
|
||||
) -> tuple[float, list[str]]:
|
||||
"""从文本中获取激活值的公共接口"""
|
||||
if not self._initialized:
|
||||
raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法")
|
||||
try:
|
||||
response, keywords,keywords_lite = await self._hippocampus.get_activate_from_text(text, max_depth, fast_retrieval)
|
||||
response, keywords, keywords_lite = await self._hippocampus.get_activate_from_text(
|
||||
text, max_depth, fast_retrieval
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"文本产生激活值失败: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
return 0.0, [],[]
|
||||
return 0.0, [], []
|
||||
|
||||
def get_memory_from_keyword(self, keyword: str, max_depth: int = 2) -> list:
|
||||
"""从关键词获取相关记忆的公共接口"""
|
||||
|
|
@ -1454,81 +1463,78 @@ hippocampus_manager = HippocampusManager()
|
|||
# 在Hippocampus类中添加新的记忆构建管理器
|
||||
class MemoryBuilder:
|
||||
"""记忆构建器
|
||||
|
||||
|
||||
为每个chat_id维护消息缓存和触发机制,类似ExpressionLearner
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, chat_id: str):
|
||||
self.chat_id = chat_id
|
||||
self.last_update_time: float = time.time()
|
||||
self.last_processed_time: float = 0.0
|
||||
|
||||
|
||||
def should_trigger_memory_build(self) -> bool:
|
||||
"""检查是否应该触发记忆构建"""
|
||||
current_time = time.time()
|
||||
|
||||
|
||||
# 检查时间间隔
|
||||
time_diff = current_time - self.last_update_time
|
||||
if time_diff < 600 /global_config.memory.memory_build_frequency:
|
||||
if time_diff < 600 / global_config.memory.memory_build_frequency:
|
||||
return False
|
||||
|
||||
|
||||
# 检查消息数量
|
||||
|
||||
|
||||
recent_messages = get_raw_msg_by_timestamp_with_chat_inclusive(
|
||||
chat_id=self.chat_id,
|
||||
timestamp_start=self.last_update_time,
|
||||
timestamp_end=current_time,
|
||||
)
|
||||
|
||||
|
||||
logger.info(f"最近消息数量: {len(recent_messages)},间隔时间: {time_diff}")
|
||||
|
||||
if not recent_messages or len(recent_messages) < 30/global_config.memory.memory_build_frequency :
|
||||
|
||||
if not recent_messages or len(recent_messages) < 30 / global_config.memory.memory_build_frequency:
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def get_messages_for_memory_build(self, threshold: int = 25) -> List[Dict[str, Any]]:
|
||||
|
||||
def get_messages_for_memory_build(self, threshold: int = 25) -> List[DatabaseMessages]:
|
||||
"""获取用于记忆构建的消息"""
|
||||
current_time = time.time()
|
||||
|
||||
|
||||
|
||||
messages = get_raw_msg_by_timestamp_with_chat_inclusive(
|
||||
chat_id=self.chat_id,
|
||||
timestamp_start=self.last_update_time,
|
||||
timestamp_end=current_time,
|
||||
limit=threshold,
|
||||
)
|
||||
tmp_msg = [msg.__dict__ for msg in messages] if messages else []
|
||||
if messages:
|
||||
# 更新最后处理时间
|
||||
self.last_processed_time = current_time
|
||||
self.last_update_time = current_time
|
||||
|
||||
return tmp_msg or []
|
||||
|
||||
return messages or []
|
||||
|
||||
|
||||
class MemorySegmentManager:
|
||||
"""记忆段管理器
|
||||
|
||||
|
||||
管理所有chat_id的MemoryBuilder实例,自动检查和触发记忆构建
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.builders: Dict[str, MemoryBuilder] = {}
|
||||
|
||||
|
||||
def get_or_create_builder(self, chat_id: str) -> MemoryBuilder:
|
||||
"""获取或创建指定chat_id的MemoryBuilder"""
|
||||
if chat_id not in self.builders:
|
||||
self.builders[chat_id] = MemoryBuilder(chat_id)
|
||||
return self.builders[chat_id]
|
||||
|
||||
|
||||
def check_and_build_memory_for_chat(self, chat_id: str) -> bool:
|
||||
"""检查指定chat_id是否需要构建记忆,如果需要则返回True"""
|
||||
builder = self.get_or_create_builder(chat_id)
|
||||
return builder.should_trigger_memory_build()
|
||||
|
||||
def get_messages_for_memory_build(self, chat_id: str, threshold: int = 25) -> List[Dict[str, Any]]:
|
||||
|
||||
def get_messages_for_memory_build(self, chat_id: str, threshold: int = 25) -> List[DatabaseMessages]:
|
||||
"""获取指定chat_id用于记忆构建的消息"""
|
||||
if chat_id not in self.builders:
|
||||
return []
|
||||
|
|
@ -1537,4 +1543,3 @@ class MemorySegmentManager:
|
|||
|
||||
# 创建全局实例
|
||||
memory_segment_manager = MemorySegmentManager()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import json
|
||||
import random
|
||||
|
||||
from json_repair import repair_json
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
from src.config.config import global_config, model_config
|
||||
from src.common.logger import get_logger
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
||||
from src.chat.memory_system.Hippocampus import hippocampus_manager
|
||||
from src.chat.utils.utils import parse_keywords_string
|
||||
from src.chat.utils.chat_message_builder import build_readable_messages
|
||||
import random
|
||||
from src.chat.memory_system.Hippocampus import hippocampus_manager
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
|
||||
|
||||
logger = get_logger("memory_activator")
|
||||
|
|
@ -75,19 +75,20 @@ class MemoryActivator:
|
|||
request_type="memory.selection",
|
||||
)
|
||||
|
||||
|
||||
async def activate_memory_with_chat_history(self, target_message, chat_history_prompt) -> List[Tuple[str, str]]:
|
||||
async def activate_memory_with_chat_history(
|
||||
self, target_message, chat_history: List[DatabaseMessages]
|
||||
) -> List[Tuple[str, str]]:
|
||||
"""
|
||||
激活记忆
|
||||
"""
|
||||
# 如果记忆系统被禁用,直接返回空列表
|
||||
if not global_config.memory.enable_memory:
|
||||
return []
|
||||
|
||||
|
||||
keywords_list = set()
|
||||
|
||||
for msg in chat_history_prompt:
|
||||
keywords = parse_keywords_string(msg.get("key_words", ""))
|
||||
|
||||
for msg in chat_history:
|
||||
keywords = parse_keywords_string(msg.key_words)
|
||||
if keywords:
|
||||
if len(keywords_list) < 30:
|
||||
# 最多容纳30个关键词
|
||||
|
|
@ -95,24 +96,22 @@ class MemoryActivator:
|
|||
logger.debug(f"提取关键词: {keywords_list}")
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
if not keywords_list:
|
||||
logger.debug("没有提取到关键词,返回空记忆列表")
|
||||
return []
|
||||
|
||||
|
||||
# 从海马体获取相关记忆
|
||||
related_memory = await hippocampus_manager.get_memory_from_topic(
|
||||
valid_keywords=list(keywords_list), max_memory_num=5, max_memory_length=3, max_depth=3
|
||||
)
|
||||
|
||||
|
||||
# logger.info(f"当前记忆关键词: {keywords_list}")
|
||||
logger.debug(f"获取到的记忆: {related_memory}")
|
||||
|
||||
|
||||
if not related_memory:
|
||||
logger.debug("海马体没有返回相关记忆")
|
||||
return []
|
||||
|
||||
|
||||
|
||||
used_ids = set()
|
||||
candidate_memories = []
|
||||
|
|
@ -120,12 +119,7 @@ class MemoryActivator:
|
|||
# 为每个记忆分配随机ID并过滤相关记忆
|
||||
for memory in related_memory:
|
||||
keyword, content = memory
|
||||
found = False
|
||||
for kw in keywords_list:
|
||||
if kw in content:
|
||||
found = True
|
||||
break
|
||||
|
||||
found = any(kw in content for kw in keywords_list)
|
||||
if found:
|
||||
# 随机分配一个不重复的2位数id
|
||||
while True:
|
||||
|
|
@ -138,94 +132,83 @@ class MemoryActivator:
|
|||
if not candidate_memories:
|
||||
logger.info("没有找到相关的候选记忆")
|
||||
return []
|
||||
|
||||
|
||||
# 如果只有少量记忆,直接返回
|
||||
if len(candidate_memories) <= 2:
|
||||
logger.debug(f"候选记忆较少({len(candidate_memories)}个),直接返回")
|
||||
# 转换为 (keyword, content) 格式
|
||||
return [(mem["keyword"], mem["content"]) for mem in candidate_memories]
|
||||
|
||||
# 使用 LLM 选择合适的记忆
|
||||
selected_memories = await self._select_memories_with_llm(target_message, chat_history_prompt, candidate_memories)
|
||||
|
||||
return selected_memories
|
||||
|
||||
async def _select_memories_with_llm(self, target_message, chat_history_prompt, candidate_memories) -> List[Tuple[str, str]]:
|
||||
return await self._select_memories_with_llm(target_message, chat_history, candidate_memories)
|
||||
|
||||
async def _select_memories_with_llm(
|
||||
self, target_message, chat_history: List[DatabaseMessages], candidate_memories
|
||||
) -> List[Tuple[str, str]]:
|
||||
"""
|
||||
使用 LLM 选择合适的记忆
|
||||
|
||||
|
||||
Args:
|
||||
target_message: 目标消息
|
||||
chat_history_prompt: 聊天历史
|
||||
candidate_memories: 候选记忆列表,每个记忆包含 memory_id、keyword、content
|
||||
|
||||
|
||||
Returns:
|
||||
List[Tuple[str, str]]: 选择的记忆列表,格式为 (keyword, content)
|
||||
"""
|
||||
try:
|
||||
# 构建聊天历史字符串
|
||||
obs_info_text = build_readable_messages(
|
||||
chat_history_prompt,
|
||||
chat_history,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="relative",
|
||||
read_mark=0.0,
|
||||
show_actions=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
# 构建记忆信息字符串
|
||||
memory_lines = []
|
||||
for memory in candidate_memories:
|
||||
memory_id = memory["memory_id"]
|
||||
keyword = memory["keyword"]
|
||||
content = memory["content"]
|
||||
|
||||
|
||||
# 将 content 列表转换为字符串
|
||||
if isinstance(content, list):
|
||||
content_str = " | ".join(str(item) for item in content)
|
||||
else:
|
||||
content_str = str(content)
|
||||
|
||||
|
||||
memory_lines.append(f"记忆编号 {memory_id}: [关键词: {keyword}] {content_str}")
|
||||
|
||||
|
||||
memory_info = "\n".join(memory_lines)
|
||||
|
||||
|
||||
# 获取并格式化 prompt
|
||||
prompt_template = await global_prompt_manager.get_prompt_async("memory_activator_prompt")
|
||||
formatted_prompt = prompt_template.format(
|
||||
obs_info_text=obs_info_text,
|
||||
target_message=target_message,
|
||||
memory_info=memory_info
|
||||
obs_info_text=obs_info_text, target_message=target_message, memory_info=memory_info
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
# 调用 LLM
|
||||
response, (reasoning_content, model_name, _) = await self.memory_selection_model.generate_response_async(
|
||||
formatted_prompt,
|
||||
temperature=0.3,
|
||||
max_tokens=150
|
||||
formatted_prompt, temperature=0.3, max_tokens=150
|
||||
)
|
||||
|
||||
|
||||
if global_config.debug.show_prompt:
|
||||
logger.info(f"记忆选择 prompt: {formatted_prompt}")
|
||||
logger.info(f"LLM 记忆选择响应: {response}")
|
||||
else:
|
||||
logger.debug(f"记忆选择 prompt: {formatted_prompt}")
|
||||
logger.debug(f"LLM 记忆选择响应: {response}")
|
||||
|
||||
|
||||
# 解析响应获取选择的记忆编号
|
||||
try:
|
||||
fixed_json = repair_json(response)
|
||||
|
||||
|
||||
# 解析为 Python 对象
|
||||
result = json.loads(fixed_json) if isinstance(fixed_json, str) else fixed_json
|
||||
|
||||
# 提取 memory_ids 字段
|
||||
memory_ids_str = result.get("memory_ids", "")
|
||||
|
||||
# 解析逗号分隔的编号
|
||||
if memory_ids_str:
|
||||
|
||||
# 提取 memory_ids 字段并解析逗号分隔的编号
|
||||
if memory_ids_str := result.get("memory_ids", ""):
|
||||
memory_ids = [mid.strip() for mid in str(memory_ids_str).split(",") if mid.strip()]
|
||||
# 过滤掉空字符串和无效编号
|
||||
valid_memory_ids = [mid for mid in memory_ids if mid and len(mid) <= 3]
|
||||
|
|
@ -235,26 +218,24 @@ class MemoryActivator:
|
|||
except Exception as e:
|
||||
logger.error(f"解析记忆选择响应失败: {e}", exc_info=True)
|
||||
selected_memory_ids = []
|
||||
|
||||
|
||||
# 根据编号筛选记忆
|
||||
selected_memories = []
|
||||
memory_id_to_memory = {mem["memory_id"]: mem for mem in candidate_memories}
|
||||
|
||||
for memory_id in selected_memory_ids:
|
||||
if memory_id in memory_id_to_memory:
|
||||
selected_memories.append(memory_id_to_memory[memory_id])
|
||||
|
||||
|
||||
selected_memories = [
|
||||
memory_id_to_memory[memory_id] for memory_id in selected_memory_ids if memory_id in memory_id_to_memory
|
||||
]
|
||||
logger.info(f"LLM 选择的记忆编号: {selected_memory_ids}")
|
||||
logger.info(f"最终选择的记忆数量: {len(selected_memories)}")
|
||||
|
||||
|
||||
# 转换为 (keyword, content) 格式
|
||||
return [(mem["keyword"], mem["content"]) for mem in selected_memories]
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"LLM 选择记忆时出错: {e}", exc_info=True)
|
||||
# 出错时返回前3个候选记忆作为备选,转换为 (keyword, content) 格式
|
||||
return [(mem["keyword"], mem["content"]) for mem in candidate_memories[:3]]
|
||||
|
||||
|
||||
|
||||
init_prompt()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from typing import List, Optional, Dict, Any, Tuple
|
|||
from datetime import datetime
|
||||
from src.mais4u.mai_think import mai_thinking_manager
|
||||
from src.common.logger import get_logger
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
from src.config.config import global_config, model_config
|
||||
from src.individuality.individuality import get_individuality
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
|
|
@ -296,7 +297,7 @@ class DefaultReplyer:
|
|||
|
||||
if not sender:
|
||||
return ""
|
||||
|
||||
|
||||
if sender == global_config.bot.nickname:
|
||||
return ""
|
||||
|
||||
|
|
@ -352,7 +353,7 @@ class DefaultReplyer:
|
|||
|
||||
return f"{expression_habits_title}\n{expression_habits_block}", selected_ids
|
||||
|
||||
async def build_memory_block(self, chat_history: List[Dict[str, Any]], target: str) -> str:
|
||||
async def build_memory_block(self, chat_history: List[DatabaseMessages], target: str) -> str:
|
||||
"""构建记忆块
|
||||
|
||||
Args:
|
||||
|
|
@ -369,7 +370,7 @@ class DefaultReplyer:
|
|||
instant_memory = None
|
||||
|
||||
running_memories = await self.memory_activator.activate_memory_with_chat_history(
|
||||
target_message=target, chat_history_prompt=chat_history
|
||||
target_message=target, chat_history=chat_history
|
||||
)
|
||||
|
||||
if global_config.memory.enable_instant_memory:
|
||||
|
|
@ -433,7 +434,7 @@ class DefaultReplyer:
|
|||
logger.error(f"工具信息获取失败: {e}")
|
||||
return ""
|
||||
|
||||
def _parse_reply_target(self, target_message: str) -> Tuple[str, str]:
|
||||
def _parse_reply_target(self, target_message: Optional[str]) -> Tuple[str, str]:
|
||||
"""解析回复目标消息
|
||||
|
||||
Args:
|
||||
|
|
@ -514,7 +515,7 @@ class DefaultReplyer:
|
|||
return name, result, duration
|
||||
|
||||
def build_s4u_chat_history_prompts(
|
||||
self, message_list_before_now: List[Dict[str, Any]], target_user_id: str, sender: str
|
||||
self, message_list_before_now: List[DatabaseMessages], target_user_id: str, sender: str
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
构建 s4u 风格的分离对话 prompt
|
||||
|
|
@ -530,16 +531,16 @@ class DefaultReplyer:
|
|||
bot_id = str(global_config.bot.qq_account)
|
||||
|
||||
# 过滤消息:分离bot和目标用户的对话 vs 其他用户的对话
|
||||
for msg_dict in message_list_before_now:
|
||||
for msg in message_list_before_now:
|
||||
try:
|
||||
msg_user_id = str(msg_dict.get("user_id"))
|
||||
reply_to = msg_dict.get("reply_to", "")
|
||||
msg_user_id = str(msg.user_info.user_id)
|
||||
reply_to = msg.reply_to
|
||||
_platform, reply_to_user_id = self._parse_reply_target(reply_to)
|
||||
if (msg_user_id == bot_id and reply_to_user_id == target_user_id) or msg_user_id == target_user_id:
|
||||
# bot 和目标用户的对话
|
||||
core_dialogue_list.append(msg_dict)
|
||||
core_dialogue_list.append(msg)
|
||||
except Exception as e:
|
||||
logger.error(f"处理消息记录时出错: {msg_dict}, 错误: {e}")
|
||||
logger.error(f"处理消息记录时出错: {msg}, 错误: {e}")
|
||||
|
||||
# 构建背景对话 prompt
|
||||
all_dialogue_prompt = ""
|
||||
|
|
@ -574,7 +575,6 @@ class DefaultReplyer:
|
|||
core_dialogue_prompt_str = build_readable_messages(
|
||||
core_dialogue_list,
|
||||
replace_bot_name=True,
|
||||
merge_messages=False,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
read_mark=0.0,
|
||||
truncate=True,
|
||||
|
|
@ -712,25 +712,20 @@ class DefaultReplyer:
|
|||
|
||||
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
|
||||
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
message_list_before_now_long = get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=chat_id,
|
||||
timestamp=time.time(),
|
||||
limit=global_config.chat.max_context_size * 1,
|
||||
)
|
||||
temp_msg_list_before_long = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now_long]
|
||||
|
||||
# TODO: 修复!
|
||||
message_list_before_short = get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=chat_id,
|
||||
timestamp=time.time(),
|
||||
limit=int(global_config.chat.max_context_size * 0.33),
|
||||
)
|
||||
temp_msg_list_before_short = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_short]
|
||||
|
||||
chat_talking_prompt_short = build_readable_messages(
|
||||
temp_msg_list_before_short,
|
||||
message_list_before_short,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="relative",
|
||||
read_mark=0.0,
|
||||
|
|
@ -743,7 +738,7 @@ class DefaultReplyer:
|
|||
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
|
||||
),
|
||||
self._time_and_run_task(self.build_relation_info(sender, target), "relation_info"),
|
||||
self._time_and_run_task(self.build_memory_block(temp_msg_list_before_short, target), "memory_block"),
|
||||
self._time_and_run_task(self.build_memory_block(message_list_before_short, target), "memory_block"),
|
||||
self._time_and_run_task(
|
||||
self.build_tool_info(chat_talking_prompt_short, sender, target, enable_tool=enable_tool), "tool_info"
|
||||
),
|
||||
|
|
@ -827,7 +822,7 @@ class DefaultReplyer:
|
|||
|
||||
# 构建分离的对话 prompt
|
||||
core_dialogue_prompt, background_dialogue_prompt = self.build_s4u_chat_history_prompts(
|
||||
temp_msg_list_before_long, user_id, sender
|
||||
message_list_before_now_long, user_id, sender
|
||||
)
|
||||
|
||||
if global_config.bot.qq_account == user_id and platform == global_config.bot.platform:
|
||||
|
|
@ -901,11 +896,8 @@ class DefaultReplyer:
|
|||
timestamp=time.time(),
|
||||
limit=min(int(global_config.chat.max_context_size * 0.33), 15),
|
||||
)
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
temp_msg_list_before_now_half = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now_half]
|
||||
chat_talking_prompt_half = build_readable_messages(
|
||||
temp_msg_list_before_now_half,
|
||||
message_list_before_now_half,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="relative",
|
||||
read_mark=0.0,
|
||||
|
|
@ -913,7 +905,7 @@ class DefaultReplyer:
|
|||
)
|
||||
|
||||
# 并行执行2个构建任务
|
||||
(expression_habits_block, selected_expressions), relation_info = await asyncio.gather(
|
||||
(expression_habits_block, _), relation_info = await asyncio.gather(
|
||||
self.build_expression_habits(chat_talking_prompt_half, target),
|
||||
self.build_relation_info(sender, target),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import copy
|
||||
from typing import Dict, Any
|
||||
from typing import Any
|
||||
|
||||
|
||||
class BaseDataModel:
|
||||
|
|
@ -7,6 +7,7 @@ class BaseDataModel:
|
|||
return copy.deepcopy(self)
|
||||
|
||||
def temporarily_transform_class_to_dict(obj: Any) -> Any:
|
||||
# sourcery skip: assign-if-exp, reintroduce-else
|
||||
"""
|
||||
将对象或容器中的 BaseDataModel 子类(类对象)或 BaseDataModel 实例
|
||||
递归转换为普通 dict,不修改原对象。
|
||||
|
|
|
|||
|
|
@ -163,11 +163,8 @@ class ChatAction:
|
|||
limit=15,
|
||||
limit_mode="last",
|
||||
)
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now]
|
||||
chat_talking_prompt = build_readable_messages(
|
||||
tmp_msgs,
|
||||
message_list_before_now,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
read_mark=0.0,
|
||||
|
|
@ -229,11 +226,8 @@ class ChatAction:
|
|||
limit=10,
|
||||
limit_mode="last",
|
||||
)
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now]
|
||||
chat_talking_prompt = build_readable_messages(
|
||||
tmp_msgs,
|
||||
message_list_before_now,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
read_mark=0.0,
|
||||
|
|
|
|||
|
|
@ -166,11 +166,9 @@ class ChatMood:
|
|||
limit=10,
|
||||
limit_mode="last",
|
||||
)
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now]
|
||||
|
||||
chat_talking_prompt = build_readable_messages(
|
||||
tmp_msgs,
|
||||
message_list_before_now,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
read_mark=0.0,
|
||||
|
|
@ -247,11 +245,9 @@ class ChatMood:
|
|||
limit=5,
|
||||
limit_mode="last",
|
||||
)
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now]
|
||||
|
||||
chat_talking_prompt = build_readable_messages(
|
||||
tmp_msgs,
|
||||
message_list_before_now,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
read_mark=0.0,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ from src.mais4u.mais4u_chat.screen_manager import screen_manager
|
|||
from src.chat.express.expression_selector import expression_selector
|
||||
from .s4u_mood_manager import mood_manager
|
||||
from src.mais4u.mais4u_chat.internal_manager import internal_manager
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
|
||||
from typing import List
|
||||
|
||||
logger = get_logger("prompt")
|
||||
|
||||
|
||||
|
|
@ -58,7 +62,7 @@ def init_prompt():
|
|||
""",
|
||||
"s4u_prompt", # New template for private CHAT chat
|
||||
)
|
||||
|
||||
|
||||
Prompt(
|
||||
"""
|
||||
你的名字是麦麦, 是千石可乐开发的程序,可以在QQ,微信等平台发言,你现在正在哔哩哔哩作为虚拟主播进行直播
|
||||
|
|
@ -95,14 +99,13 @@ class PromptBuilder:
|
|||
def __init__(self):
|
||||
self.prompt_built = ""
|
||||
self.activate_messages = ""
|
||||
|
||||
async def build_expression_habits(self, chat_stream: ChatStream, chat_history, target):
|
||||
|
||||
async def build_expression_habits(self, chat_stream: ChatStream, chat_history, target):
|
||||
style_habits = []
|
||||
|
||||
# 使用从处理器传来的选中表达方式
|
||||
# LLM模式:调用LLM选择5-10个,然后随机选5个
|
||||
selected_expressions ,_ = await expression_selector.select_suitable_expressions_llm(
|
||||
selected_expressions, _ = await expression_selector.select_suitable_expressions_llm(
|
||||
chat_stream.stream_id, chat_history, max_num=12, target_message=target
|
||||
)
|
||||
|
||||
|
|
@ -122,7 +125,6 @@ class PromptBuilder:
|
|||
if style_habits_str.strip():
|
||||
expression_habits_block += f"你可以参考以下的语言习惯,如果情景合适就使用,不要盲目使用,不要生硬使用,而是结合到表达中:\n{style_habits_str}\n\n"
|
||||
|
||||
|
||||
return expression_habits_block
|
||||
|
||||
async def build_relation_info(self, chat_stream) -> str:
|
||||
|
|
@ -148,9 +150,7 @@ class PromptBuilder:
|
|||
person_ids.append(person_id)
|
||||
|
||||
# 使用 Person 的 build_relationship 方法,设置 points_num=3 保持与原来相同的行为
|
||||
relation_info_list = [
|
||||
Person(person_id=person_id).build_relationship() for person_id in person_ids
|
||||
]
|
||||
relation_info_list = [Person(person_id=person_id).build_relationship() for person_id in person_ids]
|
||||
if relation_info := "".join(relation_info_list):
|
||||
relation_prompt = await global_prompt_manager.format_prompt(
|
||||
"relation_prompt", relation_info=relation_info
|
||||
|
|
@ -160,7 +160,7 @@ class PromptBuilder:
|
|||
async def build_memory_block(self, text: str) -> str:
|
||||
# 待更新记忆系统
|
||||
return ""
|
||||
|
||||
|
||||
related_memory = await hippocampus_manager.get_memory_from_text(
|
||||
text=text, max_memory_num=2, max_memory_length=2, max_depth=3, fast_retrieval=False
|
||||
)
|
||||
|
|
@ -176,38 +176,37 @@ class PromptBuilder:
|
|||
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=chat_stream.stream_id,
|
||||
timestamp=time.time(),
|
||||
# sourcery skip: lift-duplicated-conditional, merge-duplicate-blocks, remove-redundant-if
|
||||
limit=300,
|
||||
)
|
||||
|
||||
|
||||
talk_type = f"{message.message_info.platform}:{str(message.chat_stream.user_info.user_id)}"
|
||||
|
||||
core_dialogue_list = []
|
||||
background_dialogue_list = []
|
||||
core_dialogue_list: List[DatabaseMessages] = []
|
||||
background_dialogue_list: List[DatabaseMessages] = []
|
||||
bot_id = str(global_config.bot.qq_account)
|
||||
target_user_id = str(message.chat_stream.user_info.user_id)
|
||||
|
||||
# TODO: 修复之!
|
||||
for msg in message_list_before_now:
|
||||
try:
|
||||
msg_user_id = str(msg.user_info.user_id)
|
||||
if msg_user_id == bot_id:
|
||||
if msg.reply_to and talk_type == msg.reply_to:
|
||||
core_dialogue_list.append(msg.__dict__)
|
||||
core_dialogue_list.append(msg)
|
||||
elif msg.reply_to and talk_type != msg.reply_to:
|
||||
background_dialogue_list.append(msg.__dict__)
|
||||
background_dialogue_list.append(msg)
|
||||
# else:
|
||||
# background_dialogue_list.append(msg_dict)
|
||||
# background_dialogue_list.append(msg_dict)
|
||||
elif msg_user_id == target_user_id:
|
||||
core_dialogue_list.append(msg.__dict__)
|
||||
core_dialogue_list.append(msg)
|
||||
else:
|
||||
background_dialogue_list.append(msg.__dict__)
|
||||
background_dialogue_list.append(msg)
|
||||
except Exception as e:
|
||||
logger.error(f"无法处理历史消息记录: {msg.__dict__}, 错误: {e}")
|
||||
|
||||
background_dialogue_prompt = ""
|
||||
if background_dialogue_list:
|
||||
context_msgs = background_dialogue_list[-s4u_config.max_context_message_length:]
|
||||
context_msgs = background_dialogue_list[-s4u_config.max_context_message_length :]
|
||||
background_dialogue_prompt_str = build_readable_messages(
|
||||
context_msgs,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
|
|
@ -217,10 +216,10 @@ class PromptBuilder:
|
|||
|
||||
core_msg_str = ""
|
||||
if core_dialogue_list:
|
||||
core_dialogue_list = core_dialogue_list[-s4u_config.max_core_message_length:]
|
||||
core_dialogue_list = core_dialogue_list[-s4u_config.max_core_message_length :]
|
||||
|
||||
first_msg = core_dialogue_list[0]
|
||||
start_speaking_user_id = first_msg.get("user_id")
|
||||
start_speaking_user_id = first_msg.user_info.user_id
|
||||
if start_speaking_user_id == bot_id:
|
||||
last_speaking_user_id = bot_id
|
||||
msg_seg_str = "你的发言:\n"
|
||||
|
|
@ -229,13 +228,13 @@ class PromptBuilder:
|
|||
last_speaking_user_id = start_speaking_user_id
|
||||
msg_seg_str = "对方的发言:\n"
|
||||
|
||||
msg_seg_str += f"{time.strftime('%H:%M:%S', time.localtime(first_msg.get('time')))}: {first_msg.get('processed_plain_text')}\n"
|
||||
msg_seg_str += f"{time.strftime('%H:%M:%S', time.localtime(first_msg.time))}: {first_msg.processed_plain_text}\n"
|
||||
|
||||
all_msg_seg_list = []
|
||||
for msg in core_dialogue_list[1:]:
|
||||
speaker = msg.get("user_id")
|
||||
speaker = msg.user_info.user_id
|
||||
if speaker == last_speaking_user_id:
|
||||
msg_seg_str += f"{time.strftime('%H:%M:%S', time.localtime(msg.get('time')))}: {msg.get('processed_plain_text')}\n"
|
||||
msg_seg_str += f"{time.strftime('%H:%M:%S', time.localtime(msg.time))}: {msg.processed_plain_text}\n"
|
||||
else:
|
||||
msg_seg_str = f"{msg_seg_str}\n"
|
||||
all_msg_seg_list.append(msg_seg_str)
|
||||
|
|
@ -252,46 +251,40 @@ class PromptBuilder:
|
|||
for msg in all_msg_seg_list:
|
||||
core_msg_str += msg
|
||||
|
||||
|
||||
all_dialogue_prompt = get_raw_msg_before_timestamp_with_chat(
|
||||
all_dialogue_history = get_raw_msg_before_timestamp_with_chat(
|
||||
chat_id=chat_stream.stream_id,
|
||||
timestamp=time.time(),
|
||||
limit=20,
|
||||
)
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in all_dialogue_prompt]
|
||||
|
||||
all_dialogue_prompt_str = build_readable_messages(
|
||||
tmp_msgs,
|
||||
all_dialogue_history,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
show_pic=False,
|
||||
)
|
||||
|
||||
|
||||
return core_msg_str, background_dialogue_prompt,all_dialogue_prompt_str
|
||||
return core_msg_str, background_dialogue_prompt, all_dialogue_prompt_str
|
||||
|
||||
def build_gift_info(self, message: MessageRecvS4U):
|
||||
if message.is_gift:
|
||||
return f"这是一条礼物信息,{message.gift_name} x{message.gift_count},请注意这位用户"
|
||||
return f"这是一条礼物信息,{message.gift_name} x{message.gift_count},请注意这位用户"
|
||||
else:
|
||||
if message.is_fake_gift:
|
||||
return f"{message.processed_plain_text}(注意:这是一条普通弹幕信息,对方没有真的发送礼物,不是礼物信息,注意区分,如果对方在发假的礼物骗你,请反击)"
|
||||
|
||||
|
||||
return ""
|
||||
|
||||
def build_sc_info(self, message: MessageRecvS4U):
|
||||
super_chat_manager = get_super_chat_manager()
|
||||
return super_chat_manager.build_superchat_summary_string(message.chat_stream.stream_id)
|
||||
|
||||
|
||||
async def build_prompt_normal(
|
||||
self,
|
||||
message: MessageRecvS4U,
|
||||
message_txt: str,
|
||||
) -> str:
|
||||
|
||||
chat_stream = message.chat_stream
|
||||
|
||||
|
||||
person = Person(platform=message.chat_stream.user_info.platform, user_id=message.chat_stream.user_info.user_id)
|
||||
person_name = person.person_name
|
||||
|
||||
|
|
@ -302,28 +295,31 @@ class PromptBuilder:
|
|||
sender_name = f"[{message.chat_stream.user_info.user_nickname}]"
|
||||
else:
|
||||
sender_name = f"用户({message.chat_stream.user_info.user_id})"
|
||||
|
||||
|
||||
|
||||
relation_info_block, memory_block, expression_habits_block = await asyncio.gather(
|
||||
self.build_relation_info(chat_stream), self.build_memory_block(message_txt), self.build_expression_habits(chat_stream, message_txt, sender_name)
|
||||
self.build_relation_info(chat_stream),
|
||||
self.build_memory_block(message_txt),
|
||||
self.build_expression_habits(chat_stream, message_txt, sender_name),
|
||||
)
|
||||
|
||||
core_dialogue_prompt, background_dialogue_prompt, all_dialogue_prompt = self.build_chat_history_prompts(
|
||||
chat_stream, message
|
||||
)
|
||||
|
||||
core_dialogue_prompt, background_dialogue_prompt,all_dialogue_prompt = self.build_chat_history_prompts(chat_stream, message)
|
||||
|
||||
gift_info = self.build_gift_info(message)
|
||||
|
||||
|
||||
sc_info = self.build_sc_info(message)
|
||||
|
||||
|
||||
screen_info = screen_manager.get_screen_str()
|
||||
|
||||
|
||||
internal_state = internal_manager.get_internal_state_str()
|
||||
|
||||
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
|
||||
|
||||
mood = mood_manager.get_mood_by_chat_id(chat_stream.stream_id)
|
||||
|
||||
template_name = "s4u_prompt"
|
||||
|
||||
|
||||
if not message.is_internal:
|
||||
prompt = await global_prompt_manager.format_prompt(
|
||||
template_name,
|
||||
|
|
@ -356,7 +352,7 @@ class PromptBuilder:
|
|||
mind=message.processed_plain_text,
|
||||
mood_state=mood.mood_state,
|
||||
)
|
||||
|
||||
|
||||
# print(prompt)
|
||||
|
||||
return prompt
|
||||
|
|
|
|||
|
|
@ -99,11 +99,9 @@ class ChatMood:
|
|||
limit=int(global_config.chat.max_context_size / 3),
|
||||
limit_mode="last",
|
||||
)
|
||||
# TODO: 修复!
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now]
|
||||
|
||||
chat_talking_prompt = build_readable_messages(
|
||||
tmp_msgs,
|
||||
message_list_before_now,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
read_mark=0.0,
|
||||
|
|
@ -150,11 +148,9 @@ class ChatMood:
|
|||
limit=15,
|
||||
limit_mode="last",
|
||||
)
|
||||
# TODO: 修复
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in message_list_before_now]
|
||||
|
||||
chat_talking_prompt = build_readable_messages(
|
||||
tmp_msgs,
|
||||
message_list_before_now,
|
||||
replace_bot_name=True,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
read_mark=0.0,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
import json
|
||||
import traceback
|
||||
|
||||
from json_repair import repair_json
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from .person_info import Person
|
||||
import random
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
from src.llm_models.utils_model import LLMRequest
|
||||
from src.config.config import global_config, model_config
|
||||
from src.chat.utils.chat_message_builder import build_readable_messages
|
||||
import json
|
||||
from json_repair import repair_json
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
||||
import traceback
|
||||
from .person_info import Person
|
||||
|
||||
logger = get_logger("relation")
|
||||
|
||||
|
||||
def init_prompt():
|
||||
Prompt(
|
||||
"""
|
||||
|
|
@ -45,8 +48,7 @@ def init_prompt():
|
|||
""",
|
||||
"attitude_to_me_prompt",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Prompt(
|
||||
"""
|
||||
你的名字是{bot_name},{bot_name}的别名是{alias_str}。
|
||||
|
|
@ -80,104 +82,102 @@ def init_prompt():
|
|||
"neuroticism_prompt",
|
||||
)
|
||||
|
||||
|
||||
class RelationshipManager:
|
||||
def __init__(self):
|
||||
self.relationship_llm = LLMRequest(
|
||||
model_set=model_config.model_task_config.utils, request_type="relationship.person"
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
async def get_attitude_to_me(self, readable_messages, timestamp, person: Person):
|
||||
alias_str = ", ".join(global_config.bot.alias_names)
|
||||
current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||
# 解析当前态度值
|
||||
current_attitude_score = person.attitude_to_me
|
||||
total_confidence = person.attitude_to_me_confidence
|
||||
|
||||
|
||||
prompt = await global_prompt_manager.format_prompt(
|
||||
"attitude_to_me_prompt",
|
||||
bot_name = global_config.bot.nickname,
|
||||
alias_str = alias_str,
|
||||
person_name = person.person_name,
|
||||
nickname = person.nickname,
|
||||
readable_messages = readable_messages,
|
||||
current_time = current_time,
|
||||
bot_name=global_config.bot.nickname,
|
||||
alias_str=alias_str,
|
||||
person_name=person.person_name,
|
||||
nickname=person.nickname,
|
||||
readable_messages=readable_messages,
|
||||
current_time=current_time,
|
||||
)
|
||||
|
||||
|
||||
attitude, _ = await self.relationship_llm.generate_response_async(prompt=prompt)
|
||||
|
||||
|
||||
|
||||
attitude = repair_json(attitude)
|
||||
attitude_data = json.loads(attitude)
|
||||
|
||||
|
||||
if not attitude_data or (isinstance(attitude_data, list) and len(attitude_data) == 0):
|
||||
return ""
|
||||
|
||||
|
||||
# 确保 attitude_data 是字典格式
|
||||
if not isinstance(attitude_data, dict):
|
||||
logger.warning(f"LLM返回了错误的JSON格式,跳过解析: {type(attitude_data)}, 内容: {attitude_data}")
|
||||
return ""
|
||||
|
||||
|
||||
attitude_score = attitude_data["attitude"]
|
||||
confidence = pow(attitude_data["confidence"],2)
|
||||
|
||||
confidence = pow(attitude_data["confidence"], 2)
|
||||
|
||||
new_confidence = total_confidence + confidence
|
||||
new_attitude_score = (current_attitude_score * total_confidence + attitude_score * confidence)/new_confidence
|
||||
|
||||
new_attitude_score = (current_attitude_score * total_confidence + attitude_score * confidence) / new_confidence
|
||||
|
||||
person.attitude_to_me = new_attitude_score
|
||||
person.attitude_to_me_confidence = new_confidence
|
||||
|
||||
|
||||
return person
|
||||
|
||||
|
||||
async def get_neuroticism(self, readable_messages, timestamp, person: Person):
|
||||
alias_str = ", ".join(global_config.bot.alias_names)
|
||||
current_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||
# 解析当前态度值
|
||||
current_neuroticism_score = person.neuroticism
|
||||
total_confidence = person.neuroticism_confidence
|
||||
|
||||
|
||||
prompt = await global_prompt_manager.format_prompt(
|
||||
"neuroticism_prompt",
|
||||
bot_name = global_config.bot.nickname,
|
||||
alias_str = alias_str,
|
||||
person_name = person.person_name,
|
||||
nickname = person.nickname,
|
||||
readable_messages = readable_messages,
|
||||
current_time = current_time,
|
||||
bot_name=global_config.bot.nickname,
|
||||
alias_str=alias_str,
|
||||
person_name=person.person_name,
|
||||
nickname=person.nickname,
|
||||
readable_messages=readable_messages,
|
||||
current_time=current_time,
|
||||
)
|
||||
|
||||
neuroticism, _ = await self.relationship_llm.generate_response_async(prompt=prompt)
|
||||
|
||||
neuroticism, _ = await self.relationship_llm.generate_response_async(prompt=prompt)
|
||||
|
||||
# logger.info(f"prompt: {prompt}")
|
||||
# logger.info(f"neuroticism: {neuroticism}")
|
||||
|
||||
|
||||
neuroticism = repair_json(neuroticism)
|
||||
neuroticism_data = json.loads(neuroticism)
|
||||
|
||||
|
||||
if not neuroticism_data or (isinstance(neuroticism_data, list) and len(neuroticism_data) == 0):
|
||||
return ""
|
||||
|
||||
|
||||
# 确保 neuroticism_data 是字典格式
|
||||
if not isinstance(neuroticism_data, dict):
|
||||
logger.warning(f"LLM返回了错误的JSON格式,跳过解析: {type(neuroticism_data)}, 内容: {neuroticism_data}")
|
||||
return ""
|
||||
|
||||
|
||||
neuroticism_score = neuroticism_data["neuroticism"]
|
||||
confidence = pow(neuroticism_data["confidence"],2)
|
||||
|
||||
confidence = pow(neuroticism_data["confidence"], 2)
|
||||
|
||||
new_confidence = total_confidence + confidence
|
||||
|
||||
new_neuroticism_score = (current_neuroticism_score * total_confidence + neuroticism_score * confidence)/new_confidence
|
||||
|
||||
|
||||
new_neuroticism_score = (
|
||||
current_neuroticism_score * total_confidence + neuroticism_score * confidence
|
||||
) / new_confidence
|
||||
|
||||
person.neuroticism = new_neuroticism_score
|
||||
person.neuroticism_confidence = new_confidence
|
||||
|
||||
return person
|
||||
|
||||
|
||||
async def update_person_impression(self, person_id, timestamp, bot_engaged_messages: List[Dict[str, Any]]):
|
||||
return person
|
||||
|
||||
async def update_person_impression(self, person_id, timestamp, bot_engaged_messages: List[DatabaseMessages]):
|
||||
"""更新用户印象
|
||||
|
||||
Args:
|
||||
|
|
@ -202,12 +202,11 @@ class RelationshipManager:
|
|||
|
||||
# 遍历消息,构建映射
|
||||
for msg in user_messages:
|
||||
if msg.get("user_id") == "system":
|
||||
if msg.user_info.user_id == "system":
|
||||
continue
|
||||
try:
|
||||
|
||||
user_id = msg.get("user_id")
|
||||
platform = msg.get("chat_info_platform")
|
||||
user_id = msg.user_info.user_id
|
||||
platform = msg.chat_info.platform
|
||||
assert isinstance(user_id, str) and isinstance(platform, str)
|
||||
msg_person = Person(user_id=user_id, platform=platform)
|
||||
|
||||
|
|
@ -242,19 +241,16 @@ class RelationshipManager:
|
|||
# 确保 original_name 和 mapped_name 都不为 None
|
||||
if original_name is not None and mapped_name is not None:
|
||||
readable_messages = readable_messages.replace(f"{original_name}", f"{mapped_name}")
|
||||
|
||||
|
||||
# await self.get_points(
|
||||
# readable_messages=readable_messages, name_mapping=name_mapping, timestamp=timestamp, person=person)
|
||||
# readable_messages=readable_messages, name_mapping=name_mapping, timestamp=timestamp, person=person)
|
||||
await self.get_attitude_to_me(readable_messages=readable_messages, timestamp=timestamp, person=person)
|
||||
await self.get_neuroticism(readable_messages=readable_messages, timestamp=timestamp, person=person)
|
||||
|
||||
person.know_times = know_times + 1
|
||||
person.last_know = timestamp
|
||||
|
||||
person.sync_to_database()
|
||||
|
||||
|
||||
|
||||
person.sync_to_database()
|
||||
|
||||
def calculate_time_weight(self, point_time: str, current_time: str) -> float:
|
||||
"""计算基于时间的权重系数"""
|
||||
|
|
@ -280,6 +276,7 @@ class RelationshipManager:
|
|||
logger.error(f"计算时间权重失败: {e}")
|
||||
return 0.5 # 发生错误时返回中等权重
|
||||
|
||||
|
||||
init_prompt()
|
||||
|
||||
relationship_manager = None
|
||||
|
|
@ -290,4 +287,3 @@ def get_relationship_manager():
|
|||
if relationship_manager is None:
|
||||
relationship_manager = RelationshipManager()
|
||||
return relationship_manager
|
||||
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ def count_new_messages_for_users(chat_id: str, start_time: float, end_time: floa
|
|||
|
||||
|
||||
def build_readable_messages_to_str(
|
||||
messages: List[Dict[str, Any]],
|
||||
messages: List[DatabaseMessages],
|
||||
replace_bot_name: bool = True,
|
||||
timestamp_mode: str = "relative",
|
||||
read_mark: float = 0.0,
|
||||
|
|
@ -440,7 +440,7 @@ def build_readable_messages_to_str(
|
|||
|
||||
|
||||
async def build_readable_messages_with_details(
|
||||
messages: List[Dict[str, Any]],
|
||||
messages: List[DatabaseMessages],
|
||||
replace_bot_name: bool = True,
|
||||
timestamp_mode: str = "relative",
|
||||
truncate: bool = False,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ import random
|
|||
from typing import Tuple
|
||||
|
||||
# 导入新插件系统
|
||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
||||
from src.plugin_system import BaseAction, ActionActivationType
|
||||
|
||||
# 导入依赖的系统组件
|
||||
from src.common.logger import get_logger
|
||||
|
||||
# 导入API模块 - 标准Python包方式
|
||||
from src.plugin_system.apis import emoji_api, llm_api, message_api
|
||||
|
||||
# NoReplyAction已集成到heartFC_chat.py中,不再需要导入
|
||||
from src.config.config import global_config
|
||||
|
||||
|
|
@ -84,11 +85,8 @@ class EmojiAction(BaseAction):
|
|||
messages_text = ""
|
||||
if recent_messages:
|
||||
# 使用message_api构建可读的消息字符串
|
||||
# TODO: 修复
|
||||
from src.common.data_models import temporarily_transform_class_to_dict
|
||||
tmp_msgs = [temporarily_transform_class_to_dict(msg) for msg in recent_messages]
|
||||
messages_text = message_api.build_readable_messages(
|
||||
messages=tmp_msgs,
|
||||
messages=recent_messages,
|
||||
timestamp_mode="normal_no_YMD",
|
||||
truncate=False,
|
||||
show_actions=False,
|
||||
|
|
|
|||
Loading…
Reference in New Issue