Merge pull request #635 from na10xi27da/rel_fix

复活吧,我的关系系统
This commit is contained in:
SengokuCola
2025-04-03 22:22:44 +08:00
committed by GitHub
14 changed files with 546 additions and 420 deletions

View File

@@ -4,7 +4,7 @@ from .plugins.utils.statistic import LLMStatistics
from .plugins.moods.moods import MoodManager from .plugins.moods.moods import MoodManager
from .plugins.schedule.schedule_generator import bot_schedule from .plugins.schedule.schedule_generator import bot_schedule
from .plugins.chat.emoji_manager import emoji_manager from .plugins.chat.emoji_manager import emoji_manager
from .plugins.relationship.relationship_manager import relationship_manager from .plugins.person_info.person_info import person_info_manager
from .plugins.willing.willing_manager import willing_manager from .plugins.willing.willing_manager import willing_manager
from .plugins.chat.chat_stream import chat_manager from .plugins.chat.chat_stream import chat_manager
from .heart_flow.heartflow import heartflow from .heart_flow.heartflow import heartflow
@@ -50,14 +50,14 @@ class MainSystem:
# 初始化表情管理器 # 初始化表情管理器
emoji_manager.initialize() emoji_manager.initialize()
logger.success("表情包管理器初始化成功")
# 启动情绪管理器 # 启动情绪管理器
self.mood_manager.start_mood_update(update_interval=global_config.mood_update_interval) self.mood_manager.start_mood_update(update_interval=global_config.mood_update_interval)
logger.success("情绪管理器启动成功") logger.success("情绪管理器启动成功")
# 加载用户关系 # 检查并清除person_info冗余字段
await relationship_manager.load_all_relationships() await person_info_manager.del_all_undefined_field()
asyncio.create_task(relationship_manager._start_relationship_manager())
# 启动愿望管理器 # 启动愿望管理器
await willing_manager.ensure_started() await willing_manager.ensure_started()
@@ -107,6 +107,7 @@ class MainSystem:
self.print_mood_task(), self.print_mood_task(),
self.remove_recalled_message_task(), self.remove_recalled_message_task(),
emoji_manager.start_periodic_check(), emoji_manager.start_periodic_check(),
emoji_manager.start_periodic_register(),
self.app.run(), self.app.run(),
] ]
await asyncio.gather(*tasks) await asyncio.gather(*tasks)

View File

@@ -5,7 +5,7 @@ MaiMBot插件系统
from .chat.chat_stream import chat_manager from .chat.chat_stream import chat_manager
from .chat.emoji_manager import emoji_manager from .chat.emoji_manager import emoji_manager
from .relationship.relationship_manager import relationship_manager from .person_info.relationship_manager import relationship_manager
from .moods.moods import MoodManager from .moods.moods import MoodManager
from .willing.willing_manager import willing_manager from .willing.willing_manager import willing_manager
from .schedule.schedule_generator import bot_schedule from .schedule.schedule_generator import bot_schedule

View File

@@ -1,5 +1,5 @@
from .emoji_manager import emoji_manager from .emoji_manager import emoji_manager
from ..relationship.relationship_manager import relationship_manager from ..person_info.relationship_manager import relationship_manager
from .chat_stream import chat_manager from .chat_stream import chat_manager
from .message_sender import message_manager from .message_sender import message_manager
from ..storage.storage import MessageStorage from ..storage.storage import MessageStorage

View File

@@ -38,6 +38,8 @@ class EmojiManager:
self.llm_emotion_judge = LLM_request( self.llm_emotion_judge = LLM_request(
model=global_config.llm_emotion_judge, max_tokens=600, temperature=0.8, request_type="emoji" model=global_config.llm_emotion_judge, max_tokens=600, temperature=0.8, request_type="emoji"
) # 更高的温度更少的token后续可以根据情绪来调整温度 ) # 更高的温度更少的token后续可以根据情绪来调整温度
logger.info("启动表情包管理器")
def _ensure_emoji_dir(self): def _ensure_emoji_dir(self):
"""确保表情存储目录存在""" """确保表情存储目录存在"""
@@ -338,7 +340,7 @@ class EmojiManager:
except Exception: except Exception:
logger.exception("[错误] 扫描表情包失败") logger.exception("[错误] 扫描表情包失败")
async def _periodic_scan(self): async def start_periodic_register(self):
"""定期扫描新表情包""" """定期扫描新表情包"""
while True: while True:
logger.info("[扫描] 开始扫描新表情包...") logger.info("[扫描] 开始扫描新表情包...")

View File

@@ -67,6 +67,8 @@ class Message_Sender:
try: try:
end_point = global_config.api_urls.get(message.message_info.platform, None) end_point = global_config.api_urls.get(message.message_info.platform, None)
if end_point: if end_point:
# logger.info(f"发送消息到{end_point}")
# logger.info(message_json)
await global_api.send_message(end_point, message_json) await global_api.send_message(end_point, message_json)
else: else:
raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置请检查配置文件") raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置请检查配置文件")

View File

@@ -149,7 +149,6 @@ def get_recent_group_speaker(chat_stream_id: int, sender, limit: int = 12) -> li
db.messages.find( db.messages.find(
{"chat_id": chat_stream_id}, {"chat_id": chat_stream_id},
{ {
"chat_info": 1,
"user_info": 1, "user_info": 1,
}, },
) )
@@ -160,20 +159,17 @@ def get_recent_group_speaker(chat_stream_id: int, sender, limit: int = 12) -> li
if not recent_messages: if not recent_messages:
return [] return []
who_chat_in_group = [] # ChatStream列表 who_chat_in_group = []
duplicate_removal = []
for msg_db_data in recent_messages: for msg_db_data in recent_messages:
user_info = UserInfo.from_dict(msg_db_data["user_info"]) user_info = UserInfo.from_dict(msg_db_data["user_info"])
if ( if (
(user_info.user_id, user_info.platform) != sender (user_info.platform, user_info.user_id) != sender
and (user_info.user_id, user_info.platform) != (global_config.BOT_QQ, "qq") and user_info.user_id != global_config.BOT_QQ
and (user_info.user_id, user_info.platform) not in duplicate_removal and (user_info.platform, user_info.user_id, user_info.user_nickname) not in who_chat_in_group
and len(duplicate_removal) < 5 and len(who_chat_in_group) < 5
): # 排除重复排除消息发送者排除bot(此处bot的平台强制为了qq可能需要更改),限制加载的关系数目 ): # 排除重复排除消息发送者排除bot限制加载的关系数目
duplicate_removal.append((user_info.user_id, user_info.platform)) who_chat_in_group.append((user_info.platform, user_info.user_id, user_info.user_nickname))
chat_info = msg_db_data.get("chat_info", {})
who_chat_in_group.append(ChatStream.from_dict(chat_info))
return who_chat_in_group return who_chat_in_group
@@ -349,6 +345,15 @@ def calculate_typing_time(input_string: str, chinese_time: float = 0.2, english_
- 如果只有一个中文字符将使用3倍的中文输入时间 - 如果只有一个中文字符将使用3倍的中文输入时间
- 在所有输入结束后额外加上回车时间0.3秒 - 在所有输入结束后额外加上回车时间0.3秒
""" """
# 如果输入是列表,将其连接成字符串
if isinstance(input_string, list):
input_string = ''.join(input_string)
# 确保现在是字符串类型
if not isinstance(input_string, str):
input_string = str(input_string)
mood_manager = MoodManager.get_instance() mood_manager = MoodManager.get_instance()
# 将0-1的唤醒度映射到-1到1 # 将0-1的唤醒度映射到-1到1
mood_arousal = mood_manager.current_mood.arousal mood_arousal = mood_manager.current_mood.arousal

View File

@@ -16,6 +16,7 @@ from ...willing.willing_manager import willing_manager
from ...message import UserInfo, Seg from ...message import UserInfo, Seg
from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig
from ...chat.chat_stream import chat_manager from ...chat.chat_stream import chat_manager
from ...person_info.relationship_manager import relationship_manager
# 定义日志配置 # 定义日志配置
chat_config = LogConfig( chat_config = LogConfig(
@@ -123,6 +124,15 @@ class ReasoningChat:
) )
message_manager.add_message(bot_message) message_manager.add_message(bot_message)
async def _update_relationship(self, message, response_set):
"""更新关系情绪"""
ori_response = ",".join(response_set)
stance, emotion = await self.gpt._get_emotion_tags(ori_response, message.processed_plain_text)
await relationship_manager.calculate_update_relationship_value(
chat_stream=message.chat_stream, label=emotion, stance=stance
)
self.mood_manager.update_mood_from_emotion(emotion, global_config.mood_intensity_factor)
async def process_message(self, message_data: str) -> None: async def process_message(self, message_data: str) -> None:
"""处理消息并生成回复""" """处理消息并生成回复"""
timing_results = {} timing_results = {}
@@ -226,6 +236,12 @@ class ReasoningChat:
timer2 = time.time() timer2 = time.time()
timing_results["处理表情包"] = timer2 - timer1 timing_results["处理表情包"] = timer2 - timer1
# 更新关系情绪
timer1 = time.time()
await self._update_relationship(message, response_set)
timer2 = time.time()
timing_results["更新关系情绪"] = timer2 - timer1
# 输出性能计时结果 # 输出性能计时结果
if do_reply: if do_reply:
timing_str = " | ".join([f"{step}: {duration:.2f}" for step, duration in timing_results.items()]) timing_str = " | ".join([f"{step}: {duration:.2f}" for step, duration in timing_results.items()])

View File

@@ -7,9 +7,10 @@ from ...memory_system.Hippocampus import HippocampusManager
from ...moods.moods import MoodManager from ...moods.moods import MoodManager
from ...schedule.schedule_generator import bot_schedule from ...schedule.schedule_generator import bot_schedule
from ...config.config import global_config from ...config.config import global_config
from ...chat.utils import get_embedding, get_recent_group_detailed_plain_text from ...chat.utils import get_embedding, get_recent_group_detailed_plain_text, get_recent_group_speaker
from ...chat.chat_stream import chat_manager from ...chat.chat_stream import chat_manager
from src.common.logger import get_module_logger from src.common.logger import get_module_logger
from ...person_info.relationship_manager import relationship_manager
logger = get_module_logger("prompt") logger = get_module_logger("prompt")
@@ -25,6 +26,25 @@ class PromptBuilder:
# 开始构建prompt # 开始构建prompt
# 关系
who_chat_in_group = [(chat_stream.user_info.platform,
chat_stream.user_info.user_id,
chat_stream.user_info.user_nickname)]
who_chat_in_group += get_recent_group_speaker(
stream_id,
(chat_stream.user_info.platform, chat_stream.user_info.user_id),
limit=global_config.MAX_CONTEXT_SIZE,
)
relation_prompt = ""
for person in who_chat_in_group:
relation_prompt += await relationship_manager.build_relationship_info(person)
relation_prompt_all = (
f"{relation_prompt}关系等级越大,关系越好,请分析聊天记录,"
f"根据你和说话者{sender_name}的关系和态度进行回复,明确你的立场和情感。"
)
# 心情 # 心情
mood_manager = MoodManager.get_instance() mood_manager = MoodManager.get_instance()
mood_prompt = mood_manager.get_prompt() mood_prompt = mood_manager.get_prompt()
@@ -127,7 +147,7 @@ class PromptBuilder:
{schedule_prompt} {schedule_prompt}
{chat_target} {chat_target}
{chat_talking_prompt} {chat_talking_prompt}
现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n 现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。{relation_prompt_all}\n
你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)}{prompt_personality} 你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)}{prompt_personality}
你正在{chat_target_2},现在请你读读之前的聊天记录,{mood_prompt},然后给出日常且口语化的回复,平淡一些, 你正在{chat_target_2},现在请你读读之前的聊天记录,{mood_prompt},然后给出日常且口语化的回复,平淡一些,
尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger} 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger}

View File

@@ -17,6 +17,7 @@ from ...message import UserInfo, Seg
from src.heart_flow.heartflow import heartflow from src.heart_flow.heartflow import heartflow
from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig from src.common.logger import get_module_logger, CHAT_STYLE_CONFIG, LogConfig
from ...chat.chat_stream import chat_manager from ...chat.chat_stream import chat_manager
from ...person_info.relationship_manager import relationship_manager
# 定义日志配置 # 定义日志配置
chat_config = LogConfig( chat_config = LogConfig(
@@ -101,9 +102,13 @@ class ThinkFlowChat:
"""处理表情包""" """处理表情包"""
if random() < global_config.emoji_chance: if random() < global_config.emoji_chance:
emoji_raw = await emoji_manager.get_emoji_for_text(response) emoji_raw = await emoji_manager.get_emoji_for_text(response)
# print("11111111111111")
# logger.info(emoji_raw)
if emoji_raw: if emoji_raw:
emoji_path, description = emoji_raw emoji_path, description = emoji_raw
emoji_cq = image_path_to_base64(emoji_path) emoji_cq = image_path_to_base64(emoji_path)
# logger.info(emoji_cq)
thinking_time_point = round(message.message_info.time, 2) thinking_time_point = round(message.message_info.time, 2)
@@ -122,6 +127,8 @@ class ThinkFlowChat:
is_head=False, is_head=False,
is_emoji=True, is_emoji=True,
) )
# logger.info("22222222222222")
message_manager.add_message(bot_message) message_manager.add_message(bot_message)
async def _update_using_response(self, message, response_set): async def _update_using_response(self, message, response_set):
@@ -135,6 +142,15 @@ class ThinkFlowChat:
await heartflow.get_subheartflow(stream_id).do_thinking_after_reply(response_set, chat_talking_prompt) await heartflow.get_subheartflow(stream_id).do_thinking_after_reply(response_set, chat_talking_prompt)
async def _update_relationship(self, message, response_set):
"""更新关系情绪"""
ori_response = ",".join(response_set)
stance, emotion = await self.gpt._get_emotion_tags(ori_response, message.processed_plain_text)
await relationship_manager.calculate_update_relationship_value(
chat_stream=message.chat_stream, label=emotion, stance=stance
)
self.mood_manager.update_mood_from_emotion(emotion, global_config.mood_intensity_factor)
async def process_message(self, message_data: str) -> None: async def process_message(self, message_data: str) -> None:
"""处理消息并生成回复""" """处理消息并生成回复"""
timing_results = {} timing_results = {}
@@ -180,8 +196,10 @@ class ThinkFlowChat:
# 计算回复意愿 # 计算回复意愿
current_willing_old = willing_manager.get_willing(chat_stream=chat) current_willing_old = willing_manager.get_willing(chat_stream=chat)
current_willing_new = (heartflow.get_subheartflow(chat.stream_id).current_state.willing - 5) / 4 # current_willing_new = (heartflow.get_subheartflow(chat.stream_id).current_state.willing - 5) / 4
current_willing = (current_willing_old + current_willing_new) / 2 # current_willing = (current_willing_old + current_willing_new) / 2
# 有点bug
current_willing = current_willing_old
willing_manager.set_willing(chat.stream_id, current_willing) willing_manager.set_willing(chat.stream_id, current_willing)
@@ -263,6 +281,12 @@ class ThinkFlowChat:
timer2 = time.time() timer2 = time.time()
timing_results["更新心流"] = timer2 - timer1 timing_results["更新心流"] = timer2 - timer1
# 更新关系情绪
timer1 = time.time()
await self._update_relationship(message, response_set)
timer2 = time.time()
timing_results["更新关系情绪"] = timer2 - timer1
# 输出性能计时结果 # 输出性能计时结果
if do_reply: if do_reply:
timing_str = " | ".join([f"{step}: {duration:.2f}" for step, duration in timing_results.items()]) timing_str = " | ".join([f"{step}: {duration:.2f}" for step, duration in timing_results.items()])

View File

@@ -6,9 +6,10 @@ from ...memory_system.Hippocampus import HippocampusManager
from ...moods.moods import MoodManager from ...moods.moods import MoodManager
from ...schedule.schedule_generator import bot_schedule from ...schedule.schedule_generator import bot_schedule
from ...config.config import global_config from ...config.config import global_config
from ...chat.utils import get_recent_group_detailed_plain_text from ...chat.utils import get_recent_group_detailed_plain_text, get_recent_group_speaker
from ...chat.chat_stream import chat_manager from ...chat.chat_stream import chat_manager
from src.common.logger import get_module_logger from src.common.logger import get_module_logger
from ...person_info.relationship_manager import relationship_manager
from src.heart_flow.heartflow import heartflow from src.heart_flow.heartflow import heartflow
@@ -28,6 +29,25 @@ class PromptBuilder:
# 开始构建prompt # 开始构建prompt
# 关系
who_chat_in_group = [(chat_stream.user_info.platform,
chat_stream.user_info.user_id,
chat_stream.user_info.user_nickname)]
who_chat_in_group += get_recent_group_speaker(
stream_id,
(chat_stream.user_info.platform, chat_stream.user_info.user_id),
limit=global_config.MAX_CONTEXT_SIZE,
)
relation_prompt = ""
for person in who_chat_in_group:
relation_prompt += await relationship_manager.build_relationship_info(person)
relation_prompt_all = (
f"{relation_prompt}关系等级越大,关系越好,请分析聊天记录,"
f"根据你和说话者{sender_name}的关系和态度进行回复,明确你的立场和情感。"
)
# 心情 # 心情
mood_manager = MoodManager.get_instance() mood_manager = MoodManager.get_instance()
mood_prompt = mood_manager.get_prompt() mood_prompt = mood_manager.get_prompt()
@@ -98,18 +118,19 @@ class PromptBuilder:
logger.info("开始构建prompt") logger.info("开始构建prompt")
prompt = f""" prompt = f"""
{relation_prompt_all}\n
{chat_target} {chat_target}
{chat_talking_prompt} {chat_talking_prompt}
你刚刚脑子里在想: 你刚刚脑子里在想:
{current_mind_info} {current_mind_info}
现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n 现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。{relation_prompt_all}\n
你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)}{prompt_personality} 你的网名叫{global_config.BOT_NICKNAME},有人也叫你{"/".join(global_config.BOT_ALIAS_NAMES)}{prompt_personality}
你正在{chat_target_2},现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些, 你正在{chat_target_2},现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些,
尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger} 尽量简短一些。{keywords_reaction_prompt}请注意把握聊天内容,不要回复的太有条理,可以有个性。{prompt_ger}
请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景,尽量不要说你说过的话 请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景,尽量不要说你说过的话
请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。
{moderation_prompt}不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )。""" {moderation_prompt}不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )。"""
return prompt return prompt
def _build_initiative_prompt_select(self, group_id, probability_1=0.8, probability_2=0.1): def _build_initiative_prompt_select(self, group_id, probability_1=0.8, probability_2=0.1):

View File

@@ -5,6 +5,7 @@ from dataclasses import dataclass
from ..config.config import global_config from ..config.config import global_config
from src.common.logger import get_module_logger, LogConfig, MOOD_STYLE_CONFIG from src.common.logger import get_module_logger, LogConfig, MOOD_STYLE_CONFIG
from ..person_info.relationship_manager import relationship_manager
mood_config = LogConfig( mood_config = LogConfig(
# 使用海马体专用样式 # 使用海马体专用样式
@@ -55,15 +56,15 @@ class MoodManager:
# 情绪词映射表 (valence, arousal) # 情绪词映射表 (valence, arousal)
self.emotion_map = { self.emotion_map = {
"开心": (0.8, 0.6), # 高愉悦度,中等唤醒度 "开心": (0.21, 0.6),
"愤怒": (-0.7, 0.7), # 负愉悦度,高唤醒度 "害羞": (0.15, 0.2),
"悲伤": (-0.6, 0.3), # 负愉悦度,低唤醒度 "愤怒": (-0.24, 0.8),
"惊讶": (0.2, 0.8), # 中等愉悦度,高唤醒度 "恐惧": (-0.21, 0.7),
"害羞": (0.5, 0.2), # 中等愉悦度,低唤醒度 "悲伤": (-0.21, 0.3),
"平静": (0.0, 0.5), # 中性愉悦度,中等唤醒度 "厌恶": (-0.12, 0.4),
"恐惧": (-0.7, 0.6), # 负愉悦度,高唤醒度 "惊讶": (0.06, 0.7),
"厌恶": (-0.4, 0.4), # 负愉悦度,低唤醒度 "困惑": (0.0, 0.6),
"困惑": (0.0, 0.6), # 中性愉悦度,高唤醒度 "平静": (0.03, 0.5),
} }
# 情绪文本映射表 # 情绪文本映射表
@@ -93,7 +94,7 @@ class MoodManager:
cls._instance = MoodManager() cls._instance = MoodManager()
return cls._instance return cls._instance
def start_mood_update(self, update_interval: float = 1.0) -> None: def start_mood_update(self, update_interval: float = 5.0) -> None:
""" """
启动情绪更新线程 启动情绪更新线程
:param update_interval: 更新间隔(秒) :param update_interval: 更新间隔(秒)
@@ -228,9 +229,15 @@ class MoodManager:
:param intensity: 情绪强度0.0-1.0 :param intensity: 情绪强度0.0-1.0
""" """
if emotion not in self.emotion_map: if emotion not in self.emotion_map:
logger.debug(f"[情绪更新] 未知情绪词: {emotion}")
return return
valence_change, arousal_change = self.emotion_map[emotion] valence_change, arousal_change = self.emotion_map[emotion]
old_valence = self.current_mood.valence
old_arousal = self.current_mood.arousal
old_mood = self.current_mood.text
valence_change *= relationship_manager.gain_coefficient[relationship_manager.positive_feedback_value]
# 应用情绪强度 # 应用情绪强度
valence_change *= intensity valence_change *= intensity
@@ -243,5 +250,8 @@ class MoodManager:
# 限制范围 # 限制范围
self.current_mood.valence = max(-1.0, min(1.0, self.current_mood.valence)) self.current_mood.valence = max(-1.0, min(1.0, self.current_mood.valence))
self.current_mood.arousal = max(0.0, min(1.0, self.current_mood.arousal)) self.current_mood.arousal = max(0.0, min(1.0, self.current_mood.arousal))
self._update_mood_text() self._update_mood_text()
logger.info(f"[情绪变化] {emotion}(强度:{intensity:.2f}) | 愉悦度:{old_valence:.2f}->{self.current_mood.valence:.2f}, 唤醒度:{old_arousal:.2f}->{self.current_mood.arousal:.2f} | 心情:{old_mood}->{self.current_mood.text}")

View File

@@ -0,0 +1,213 @@
from src.common.logger import get_module_logger
from ...common.database import db
import copy
import hashlib
from typing import Any, Callable, Dict, TypeVar
T = TypeVar('T') # 泛型类型
"""
PersonInfoManager 类方法功能摘要:
1. get_person_id - 根据平台和用户ID生成MD5哈希的唯一person_id
2. create_person_info - 创建新个人信息文档(自动合并默认值)
3. update_one_field - 更新单个字段值(若文档不存在则创建)
4. del_one_document - 删除指定person_id的文档
5. get_value - 获取单个字段值(返回实际值或默认值)
6. get_values - 批量获取字段值(任一字段无效则返回空字典)
7. del_all_undefined_field - 清理全集合中未定义的字段
8. get_specific_value_list - 根据指定条件返回person_id,value字典
"""
logger = get_module_logger("person_info")
person_info_default = {
"person_id" : None,
"platform" : None,
"user_id" : None,
"nickname" : None,
# "age" : 0,
"relationship_value" : 0,
# "saved" : True,
# "impression" : None,
# "gender" : Unkown,
"konw_time" : 0,
} # 个人信息的各项与默认值在此定义,以下处理会自动创建/补全每一项
class PersonInfoManager:
def __init__(self):
if "person_info" not in db.list_collection_names():
db.create_collection("person_info")
db.person_info.create_index("person_id", unique=True)
def get_person_id(self, platform:str, user_id:int):
"""获取唯一id"""
components = [platform, str(user_id)]
key = "_".join(components)
return hashlib.md5(key.encode()).hexdigest()
async def create_person_info(self, person_id:str, data:dict = None):
"""创建一个项"""
if not person_id:
logger.debug("创建失败personid不存在")
return
_person_info_default = copy.deepcopy(person_info_default)
_person_info_default["person_id"] = person_id
if data:
for key in _person_info_default:
if key != "person_id" and key in data:
_person_info_default[key] = data[key]
db.person_info.insert_one(_person_info_default)
async def update_one_field(self, person_id:str, field_name:str, value, Data:dict = None):
"""更新某一个字段,会补全"""
if field_name not in person_info_default.keys():
logger.debug(f"更新'{field_name}'失败,未定义的字段")
return
document = db.person_info.find_one({"person_id": person_id})
if document:
db.person_info.update_one(
{"person_id": person_id},
{"$set": {field_name: value}}
)
else:
Data[field_name] = value
logger.debug(f"更新时{person_id}不存在,已新建")
await self.create_person_info(person_id, Data)
async def del_one_document(self, person_id: str):
"""删除指定 person_id 的文档"""
if not person_id:
logger.debug("删除失败person_id 不能为空")
return
result = db.person_info.delete_one({"person_id": person_id})
if result.deleted_count > 0:
logger.debug(f"删除成功person_id={person_id}")
else:
logger.debug(f"删除失败:未找到 person_id={person_id}")
async def get_value(self, person_id: str, field_name: str):
"""获取指定person_id文档的字段值若不存在该字段则返回该字段的全局默认值"""
if not person_id:
logger.debug("get_value获取失败person_id不能为空")
return None
if field_name not in person_info_default:
logger.debug(f"get_value获取失败字段'{field_name}'未定义")
return None
document = db.person_info.find_one(
{"person_id": person_id},
{field_name: 1}
)
if document and field_name in document:
return document[field_name]
else:
logger.debug(f"获取{person_id}{field_name}失败,已返回默认值{person_info_default[field_name]}")
return person_info_default[field_name]
async def get_values(self, person_id: str, field_names: list) -> dict:
"""获取指定person_id文档的多个字段值若不存在该字段则返回该字段的全局默认值"""
if not person_id:
logger.debug("get_values获取失败person_id不能为空")
return {}
# 检查所有字段是否有效
for field in field_names:
if field not in person_info_default:
logger.debug(f"get_values获取失败字段'{field}'未定义")
return {}
# 构建查询投影(所有字段都有效才会执行到这里)
projection = {field: 1 for field in field_names}
document = db.person_info.find_one(
{"person_id": person_id},
projection
)
result = {}
for field in field_names:
result[field] = document.get(field, person_info_default[field]) if document else person_info_default[field]
return result
async def del_all_undefined_field(self):
"""删除所有项里的未定义字段"""
# 获取所有已定义的字段名
defined_fields = set(person_info_default.keys())
try:
# 遍历集合中的所有文档
for document in db.person_info.find({}):
# 找出文档中未定义的字段
undefined_fields = set(document.keys()) - defined_fields - {'_id'}
if undefined_fields:
# 构建更新操作,使用$unset删除未定义字段
update_result = db.person_info.update_one(
{'_id': document['_id']},
{'$unset': {field: 1 for field in undefined_fields}}
)
if update_result.modified_count > 0:
logger.debug(f"已清理文档 {document['_id']} 的未定义字段: {undefined_fields}")
return
except Exception as e:
logger.error(f"清理未定义字段时出错: {e}")
return
async def get_specific_value_list(
self,
field_name: str,
way: Callable[[Any], bool], # 接受任意类型值
) ->Dict[str, Any]:
"""
获取满足条件的字段值字典
Args:
field_name: 目标字段名
way: 判断函数 (value: Any) -> bool
Returns:
{person_id: value} | {}
Example:
# 查找所有nickname包含"admin"的用户
result = manager.specific_value_list(
"nickname",
lambda x: "admin" in x.lower()
)
"""
if field_name not in person_info_default:
logger.error(f"字段检查失败:'{field_name}'未定义")
return {}
try:
result = {}
for doc in db.person_info.find(
{field_name: {"$exists": True}},
{"person_id": 1, field_name: 1, "_id": 0}
):
try:
value = doc[field_name]
if way(value):
result[doc["person_id"]] = value
except (KeyError, TypeError, ValueError) as e:
logger.debug(f"记录{doc.get('person_id')}处理失败: {str(e)}")
continue
return result
except Exception as e:
logger.error(f"数据库查询失败: {str(e)}", exc_info=True)
return {}
person_info_manager = PersonInfoManager()

View File

@@ -0,0 +1,195 @@
from src.common.logger import get_module_logger, LogConfig, RELATION_STYLE_CONFIG
from ..chat.chat_stream import ChatStream
import math
from bson.decimal128 import Decimal128
from .person_info import person_info_manager
import time
relationship_config = LogConfig(
# 使用关系专用样式
console_format=RELATION_STYLE_CONFIG["console_format"],
file_format=RELATION_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("rel_manager", config=relationship_config)
class RelationshipManager:
def __init__(self):
self.positive_feedback_value = 0 # 正反馈系统
self.gain_coefficient = [1.0, 1.0, 1.1, 1.2, 1.4, 1.7, 1.9, 2.0]
self._mood_manager = None
@property
def mood_manager(self):
if self._mood_manager is None:
from ..moods.moods import MoodManager # 延迟导入
self._mood_manager = MoodManager.get_instance()
return self._mood_manager
def positive_feedback_sys(self, label: str, stance: str):
"""正反馈系统,通过正反馈系数增益情绪变化,根据情绪再影响关系变更"""
positive_list = [
"开心",
"惊讶",
"害羞",
]
negative_list = [
"愤怒",
"悲伤",
"恐惧",
"厌恶",
]
if label in positive_list and stance != "反对":
if 7 > self.positive_feedback_value >= 0:
self.positive_feedback_value += 1
elif self.positive_feedback_value < 0:
self.positive_feedback_value = 0
elif label in negative_list and stance != "支持":
if -7 < self.positive_feedback_value <= 0:
self.positive_feedback_value -= 1
elif self.positive_feedback_value > 0:
self.positive_feedback_value = 0
if abs(self.positive_feedback_value) > 1:
logger.info(f"触发mood变更增益当前增益系数{self.gain_coefficient[abs(self.positive_feedback_value)]}")
def mood_feedback(self, value):
"""情绪反馈"""
mood_manager = self.mood_manager
mood_gain = (mood_manager.get_current_mood().valence) ** 2 \
* math.copysign(1, value * mood_manager.get_current_mood().valence)
value += value * mood_gain
logger.info(f"当前relationship增益系数{mood_gain:.3f}")
return value
async def calculate_update_relationship_value(self, chat_stream: ChatStream, label: str, stance: str) -> None:
"""计算并变更关系值
新的关系值变更计算方式:
将关系值限定在-1000到1000
对于关系值的变更,期望:
1.向两端逼近时会逐渐减缓
2.关系越差,改善越难,关系越好,恶化越容易
3.人维护关系的精力往往有限,所以当高关系值用户越多,对于中高关系值用户增长越慢
4.连续正面或负面情感会正反馈
"""
stancedict = {
"支持": 0,
"中立": 1,
"反对": 2,
}
valuedict = {
"开心": 1.5,
"愤怒": -2.0,
"悲伤": -0.5,
"惊讶": 0.6,
"害羞": 2.0,
"平静": 0.3,
"恐惧": -1.5,
"厌恶": -1.0,
"困惑": 0.5,
}
person_id = person_info_manager.get_person_id(chat_stream.user_info.platform, chat_stream.user_info.user_id)
data = {
"platform" : chat_stream.user_info.platform,
"user_id" : chat_stream.user_info.user_id,
"nickname" : chat_stream.user_info.user_nickname,
"konw_time" : int(time.time())
}
old_value = await person_info_manager.get_value(person_id, "relationship_value")
old_value = self.ensure_float(old_value, person_id)
if old_value > 1000:
old_value = 1000
elif old_value < -1000:
old_value = -1000
value = valuedict[label]
if old_value >= 0:
if valuedict[label] >= 0 and stancedict[stance] != 2:
value = value * math.cos(math.pi * old_value / 2000)
if old_value > 500:
rdict = await person_info_manager.get_specific_value_list("relationship_value", lambda x: x > 700)
high_value_count = len(rdict)
if old_value > 700:
value *= 3 / (high_value_count + 2) # 排除自己
else:
value *= 3 / (high_value_count + 3)
elif valuedict[label] < 0 and stancedict[stance] != 0:
value = value * math.exp(old_value / 2000)
else:
value = 0
elif old_value < 0:
if valuedict[label] >= 0 and stancedict[stance] != 2:
value = value * math.exp(old_value / 2000)
elif valuedict[label] < 0 and stancedict[stance] != 0:
value = value * math.cos(math.pi * old_value / 2000)
else:
value = 0
self.positive_feedback_sys(label, stance)
value = self.mood_feedback(value)
level_num = self.calculate_level_num(old_value + value)
relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"]
logger.info(
f"当前关系: {relationship_level[level_num]}, "
f"关系值: {old_value:.2f}, "
f"当前立场情感: {stance}-{label}, "
f"变更: {value:+.5f}"
)
await person_info_manager.update_one_field(person_id, "relationship_value", old_value + value, data)
async def build_relationship_info(self, person) -> str:
person_id = person_info_manager.get_person_id(person[0], person[1])
relationship_value = await person_info_manager.get_value(person_id, "relationship_value")
level_num = self.calculate_level_num(relationship_value)
relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"]
relation_prompt2_list = [
"厌恶回应",
"冷淡回复",
"保持理性",
"愿意回复",
"积极回复",
"无条件支持",
]
return (
f"你对昵称为'({person[1]}){person[2]}'的用户的态度为{relationship_level[level_num]}"
f"回复态度为{relation_prompt2_list[level_num]},关系等级为{level_num}"
)
def calculate_level_num(self, relationship_value) -> int:
"""关系等级计算"""
if -1000 <= relationship_value < -227:
level_num = 0
elif -227 <= relationship_value < -73:
level_num = 1
elif -73 <= relationship_value < 227:
level_num = 2
elif 227 <= relationship_value < 587:
level_num = 3
elif 587 <= relationship_value < 900:
level_num = 4
elif 900 <= relationship_value <= 1000:
level_num = 5
else:
level_num = 5 if relationship_value > 1000 else 0
return level_num
def ensure_float(self, value, person_id):
"""确保返回浮点数转换失败返回0.0"""
if isinstance(value, float):
return value
try:
return float(value.to_decimal() if isinstance(value, Decimal128) else value)
except (ValueError, TypeError, AttributeError):
logger.warning(f"[关系管理] {person_id}值转换失败(原始值:{value}已重置为0")
return 0.0
relationship_manager = RelationshipManager()

View File

@@ -1,383 +0,0 @@
import asyncio
from typing import Optional
from src.common.logger import get_module_logger, LogConfig, RELATION_STYLE_CONFIG
from ...common.database import db
from ..message.message_base import UserInfo
from ..chat.chat_stream import ChatStream
import math
from bson.decimal128 import Decimal128
relationship_config = LogConfig(
# 使用关系专用样式
console_format=RELATION_STYLE_CONFIG["console_format"],
file_format=RELATION_STYLE_CONFIG["file_format"],
)
logger = get_module_logger("rel_manager", config=relationship_config)
class Impression:
traits: str = None
called: str = None
know_time: float = None
relationship_value: float = None
class Relationship:
user_id: int = None
platform: str = None
gender: str = None
age: int = None
nickname: str = None
relationship_value: float = None
saved = False
def __init__(self, chat: ChatStream = None, data: dict = None):
self.user_id = chat.user_info.user_id if chat else data.get("user_id", 0)
self.platform = chat.platform if chat else data.get("platform", "")
self.nickname = chat.user_info.user_nickname if chat else data.get("nickname", "")
self.relationship_value = data.get("relationship_value", 0) if data else 0
self.age = data.get("age", 0) if data else 0
self.gender = data.get("gender", "") if data else ""
class RelationshipManager:
def __init__(self):
self.relationships: dict[tuple[int, str], Relationship] = {} # 修改为使用(user_id, platform)作为键
async def update_relationship(self, chat_stream: ChatStream, data: dict = None, **kwargs) -> Optional[Relationship]:
"""更新或创建关系
Args:
chat_stream: 聊天流对象
data: 字典格式的数据(可选)
**kwargs: 其他参数
Returns:
Relationship: 关系对象
"""
# 确定user_id和platform
if chat_stream.user_info is not None:
user_id = chat_stream.user_info.user_id
platform = chat_stream.user_info.platform or "qq"
else:
platform = platform or "qq"
if user_id is None:
raise ValueError("必须提供user_id或user_info")
# 使用(user_id, platform)作为键
key = (user_id, platform)
# 检查是否在内存中已存在
relationship = self.relationships.get(key)
if relationship:
# 如果存在,更新现有对象
if isinstance(data, dict):
for k, value in data.items():
if hasattr(relationship, k) and value is not None:
setattr(relationship, k, value)
else:
# 如果不存在,创建新对象
if chat_stream.user_info is not None:
relationship = Relationship(chat=chat_stream, **kwargs)
else:
raise ValueError("必须提供user_id或user_info")
self.relationships[key] = relationship
# 保存到数据库
await self.storage_relationship(relationship)
relationship.saved = True
return relationship
async def update_relationship_value(self, chat_stream: ChatStream, **kwargs) -> Optional[Relationship]:
"""更新关系值
Args:
user_id: 用户ID可选如果提供user_info则不需要
platform: 平台可选如果提供user_info则不需要
user_info: 用户信息对象(可选)
**kwargs: 其他参数
Returns:
Relationship: 关系对象
"""
# 确定user_id和platform
user_info = chat_stream.user_info
if user_info is not None:
user_id = user_info.user_id
platform = user_info.platform or "qq"
else:
platform = platform or "qq"
if user_id is None:
raise ValueError("必须提供user_id或user_info")
# 使用(user_id, platform)作为键
key = (user_id, platform)
# 检查是否在内存中已存在
relationship = self.relationships.get(key)
if relationship:
for k, value in kwargs.items():
if k == "relationship_value":
# 检查relationship.relationship_value是否为double类型
if not isinstance(relationship.relationship_value, float):
try:
# 处理 Decimal128 类型
if isinstance(relationship.relationship_value, Decimal128):
relationship.relationship_value = float(relationship.relationship_value.to_decimal())
else:
relationship.relationship_value = float(relationship.relationship_value)
logger.info(
f"[关系管理] 用户 {user_id}({platform}) 的关系值已转换为double类型: {relationship.relationship_value}"
) # noqa: E501
except (ValueError, TypeError):
# 如果不能解析/强转则将relationship.relationship_value设置为double类型的0
relationship.relationship_value = 0.0
logger.warning(f"[关系管理] 用户 {user_id}({platform}) 的无法转换为double类型已设置为0")
relationship.relationship_value += value
await self.storage_relationship(relationship)
relationship.saved = True
return relationship
else:
# 如果不存在且提供了user_info则创建新的关系
if user_info is not None:
return await self.update_relationship(chat_stream=chat_stream, **kwargs)
logger.warning(f"[关系管理] 用户 {user_id}({platform}) 不存在,无法更新")
return None
def get_relationship(self, chat_stream: ChatStream) -> Optional[Relationship]:
"""获取用户关系对象
Args:
user_id: 用户ID可选如果提供user_info则不需要
platform: 平台可选如果提供user_info则不需要
user_info: 用户信息对象(可选)
Returns:
Relationship: 关系对象
"""
# 确定user_id和platform
user_info = chat_stream.user_info
platform = chat_stream.user_info.platform or "qq"
if user_info is not None:
user_id = user_info.user_id
platform = user_info.platform or "qq"
else:
platform = platform or "qq"
if user_id is None:
raise ValueError("必须提供user_id或user_info")
key = (user_id, platform)
if key in self.relationships:
return self.relationships[key]
else:
return None
async def load_relationship(self, data: dict) -> Relationship:
"""从数据库加载或创建新的关系对象"""
# 确保data中有platform字段如果没有则默认为'qq'
if "platform" not in data:
data["platform"] = "qq"
rela = Relationship(data=data)
rela.saved = True
key = (rela.user_id, rela.platform)
self.relationships[key] = rela
return rela
async def load_all_relationships(self):
"""加载所有关系对象"""
all_relationships = db.relationships.find({})
for data in all_relationships:
await self.load_relationship(data)
async def _start_relationship_manager(self):
"""每5分钟自动保存一次关系数据"""
# 获取所有关系记录
all_relationships = db.relationships.find({})
# 依次加载每条记录
for data in all_relationships:
await self.load_relationship(data)
logger.debug(f"[关系管理] 已加载 {len(self.relationships)} 条关系记录")
while True:
logger.debug("正在自动保存关系")
await asyncio.sleep(300) # 等待300秒(5分钟)
await self._save_all_relationships()
async def _save_all_relationships(self):
"""将所有关系数据保存到数据库"""
# 保存所有关系数据
for _, relationship in self.relationships.items():
if not relationship.saved:
relationship.saved = True
await self.storage_relationship(relationship)
async def storage_relationship(self, relationship: Relationship):
"""将关系记录存储到数据库中"""
user_id = relationship.user_id
platform = relationship.platform
nickname = relationship.nickname
relationship_value = relationship.relationship_value
gender = relationship.gender
age = relationship.age
saved = relationship.saved
db.relationships.update_one(
{"user_id": user_id, "platform": platform},
{
"$set": {
"platform": platform,
"nickname": nickname,
"relationship_value": relationship_value,
"gender": gender,
"age": age,
"saved": saved,
}
},
upsert=True,
)
def get_name(self, user_id: int = None, platform: str = None, user_info: UserInfo = None) -> str:
"""获取用户昵称
Args:
user_id: 用户ID可选如果提供user_info则不需要
platform: 平台可选如果提供user_info则不需要
user_info: 用户信息对象(可选)
Returns:
str: 用户昵称
"""
# 确定user_id和platform
if user_info is not None:
user_id = user_info.user_id
platform = user_info.platform or "qq"
else:
platform = platform or "qq"
if user_id is None:
raise ValueError("必须提供user_id或user_info")
# 确保user_id是整数类型
user_id = int(user_id)
key = (user_id, platform)
if key in self.relationships:
return self.relationships[key].nickname
elif user_info is not None:
return user_info.user_nickname or user_info.user_cardname or "某人"
else:
return "某人"
async def calculate_update_relationship_value(self, chat_stream: ChatStream, label: str, stance: str) -> None:
"""计算变更关系值
新的关系值变更计算方式:
将关系值限定在-1000到1000
对于关系值的变更,期望:
1.向两端逼近时会逐渐减缓
2.关系越差,改善越难,关系越好,恶化越容易
3.人维护关系的精力往往有限,所以当高关系值用户越多,对于中高关系值用户增长越慢
"""
stancedict = {
"支持": 0,
"中立": 1,
"反对": 2,
}
valuedict = {
"开心": 1.5,
"愤怒": -3.5,
"悲伤": -1.5,
"惊讶": 0.6,
"害羞": 2.0,
"平静": 0.3,
"恐惧": -2,
"厌恶": -2.5,
"困惑": 0.5,
}
if self.get_relationship(chat_stream):
old_value = self.get_relationship(chat_stream).relationship_value
else:
return
if old_value > 1000:
old_value = 1000
elif old_value < -1000:
old_value = -1000
value = valuedict[label]
if old_value >= 0:
if valuedict[label] >= 0 and stancedict[stance] != 2:
value = value * math.cos(math.pi * old_value / 2000)
if old_value > 500:
high_value_count = 0
for _, relationship in self.relationships.items():
if relationship.relationship_value >= 700:
high_value_count += 1
if old_value >= 700:
value *= 3 / (high_value_count + 2) # 排除自己
else:
value *= 3 / (high_value_count + 3)
elif valuedict[label] < 0 and stancedict[stance] != 0:
value = value * math.exp(old_value / 1000)
else:
value = 0
elif old_value < 0:
if valuedict[label] >= 0 and stancedict[stance] != 2:
value = value * math.exp(old_value / 1000)
elif valuedict[label] < 0 and stancedict[stance] != 0:
value = value * math.cos(math.pi * old_value / 2000)
else:
value = 0
level_num = self.calculate_level_num(old_value + value)
relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"]
logger.info(
f"当前关系: {relationship_level[level_num]}, "
f"关系值: {old_value:.2f}, "
f"当前立场情感: {stance}-{label}, "
f"变更: {value:+.5f}"
)
await self.update_relationship_value(chat_stream=chat_stream, relationship_value=value)
def build_relationship_info(self, person) -> str:
relationship_value = relationship_manager.get_relationship(person).relationship_value
level_num = self.calculate_level_num(relationship_value)
relationship_level = ["厌恶", "冷漠", "一般", "友好", "喜欢", "暧昧"]
relation_prompt2_list = [
"冷漠回应",
"冷淡回复",
"保持理性",
"愿意回复",
"积极回复",
"无条件支持",
]
if person.user_info.user_cardname:
return (
f"你对昵称为'[({person.user_info.user_id}){person.user_info.user_nickname}]{person.user_info.user_cardname}'的用户的态度为{relationship_level[level_num]}"
f"回复态度为{relation_prompt2_list[level_num]},关系等级为{level_num}"
)
else:
return (
f"你对昵称为'({person.user_info.user_id}){person.user_info.user_nickname}'的用户的态度为{relationship_level[level_num]}"
f"回复态度为{relation_prompt2_list[level_num]},关系等级为{level_num}"
)
def calculate_level_num(self, relationship_value) -> int:
"""关系等级计算"""
if -1000 <= relationship_value < -227:
level_num = 0
elif -227 <= relationship_value < -73:
level_num = 1
elif -73 <= relationship_value < 227:
level_num = 2
elif 227 <= relationship_value < 587:
level_num = 3
elif 587 <= relationship_value < 900:
level_num = 4
elif 900 <= relationship_value <= 1000:
level_num = 5
else:
level_num = 5 if relationship_value > 1000 else 0
return level_num
relationship_manager = RelationshipManager()