From 4e73f66dce3e3a84af5b55af9eb4973985a1c170 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Wed, 19 Mar 2025 10:08:38 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A5=BF=E6=96=87?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E5=8F=A5=E5=AD=90=E9=94=99=E8=AF=AF=E5=88=86?= =?UTF-8?q?=E8=A1=8C=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/utils.py | 58 +++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index 4bbdd85c8..d64a1e59b 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -226,6 +226,13 @@ def get_recent_group_speaker(chat_stream_id: int, sender, limit: int = 12) -> li who_chat_in_group.append(ChatStream.from_dict(chat_info)) return who_chat_in_group +def is_western_char(char): + """检测是否为西文字符""" + return len(char.encode('utf-8')) <= 2 + +def is_western_paragraph(paragraph): + """检测是否为西文字符段落""" + return all(is_western_char(char) for char in paragraph if char.isalnum()) def split_into_sentences_w_remove_punctuation(text: str) -> List[str]: """将文本分割成句子,但保持书名号中的内容完整 @@ -251,8 +258,13 @@ def split_into_sentences_w_remove_punctuation(text: str) -> List[str]: # print(f"处理前的文本: {text}") - # 统一将英文逗号转换为中文逗号 - text = text.replace(',', ',') + # 检查是否为西文字符段落 + if not is_western_paragraph(text): + # 当语言为中文时,统一将英文逗号转换为中文逗号 + text = text.replace(',', ',') + else: + # 用"|seg|"作为分割符分开 + text = re.sub(r'([.!?]) +', r'\1\|seg\|', text) text = text.replace('\n', ' ') text, mapping = protect_kaomoji(text) # print(f"处理前的文本: {text}") @@ -276,21 +288,29 @@ def split_into_sentences_w_remove_punctuation(text: str) -> List[str]: for sentence in sentences: parts = sentence.split(',') current_sentence = parts[0] - for part in parts[1:]: - if random.random() < split_strength: + if not is_western_paragraph(current_sentence): + for part in parts[1:]: + if random.random() < split_strength: + new_sentences.append(current_sentence.strip()) + current_sentence = part + else: + current_sentence += ',' + part + # 处理空格分割 + space_parts = current_sentence.split(' ') + current_sentence = space_parts[0] + for part in space_parts[1:]: + if random.random() < split_strength: + new_sentences.append(current_sentence.strip()) + current_sentence = part + else: + current_sentence += ' ' + part + else: + # 处理分割符 + space_parts = current_sentence.split('\|seg\|') + current_sentence = space_parts[0] + for part in space_parts[1:]: new_sentences.append(current_sentence.strip()) current_sentence = part - else: - current_sentence += ',' + part - # 处理空格分割 - space_parts = current_sentence.split(' ') - current_sentence = space_parts[0] - for part in space_parts[1:]: - if random.random() < split_strength: - new_sentences.append(current_sentence.strip()) - current_sentence = part - else: - current_sentence += ' ' + part new_sentences.append(current_sentence.strip()) sentences = [s for s in new_sentences if s] # 移除空字符串 sentences = recover_kaomoji(sentences, mapping) @@ -338,7 +358,11 @@ def random_remove_punctuation(text: str) -> str: def process_llm_response(text: str) -> List[str]: # processed_response = process_text_with_typos(content) - if len(text) > 100: + # 对西文字符段落的回复长度设置为汉字字符的两倍 + if len(text) > 100 and not is_western_paragraph(text) : + logger.warning(f"回复过长 ({len(text)} 字符),返回默认回复") + return ['懒得说'] + elif len(text) > 200 : logger.warning(f"回复过长 ({len(text)} 字符),返回默认回复") return ['懒得说'] # 处理长消息 @@ -499,4 +523,4 @@ def recover_kaomoji(sentences, placeholder_to_kaomoji): for placeholder, kaomoji in placeholder_to_kaomoji.items(): sentence = sentence.replace(placeholder, kaomoji) recovered_sentences.append(sentence) - return recovered_sentences \ No newline at end of file + return recovered_sentences From 50d22399e08f5b586432aea7fb0d9c7a891a5918 Mon Sep 17 00:00:00 2001 From: dax <88696221+Dax233@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:15:12 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A5=BF=E6=96=87?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E9=94=99=E8=AF=AF=E5=88=86=E8=A1=8C=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/utils.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index d64a1e59b..652dec4f9 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -226,13 +226,6 @@ def get_recent_group_speaker(chat_stream_id: int, sender, limit: int = 12) -> li who_chat_in_group.append(ChatStream.from_dict(chat_info)) return who_chat_in_group -def is_western_char(char): - """检测是否为西文字符""" - return len(char.encode('utf-8')) <= 2 - -def is_western_paragraph(paragraph): - """检测是否为西文字符段落""" - return all(is_western_char(char) for char in paragraph if char.isalnum()) def split_into_sentences_w_remove_punctuation(text: str) -> List[str]: """将文本分割成句子,但保持书名号中的内容完整 @@ -524,3 +517,11 @@ def recover_kaomoji(sentences, placeholder_to_kaomoji): sentence = sentence.replace(placeholder, kaomoji) recovered_sentences.append(sentence) return recovered_sentences + +def is_western_char(char): + """检测是否为西文字符""" + return len(char.encode('utf-8')) <= 2 + +def is_western_paragraph(paragraph): + """检测是否为西文字符段落""" + return all(is_western_char(char) for char in paragraph if char.isalnum()) From 61007ffc5e2d648a7990689502c055bc68cea6c7 Mon Sep 17 00:00:00 2001 From: dax <88696221+Dax233@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:28:07 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A5=BF=E6=96=87?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E5=8F=A5=E5=AD=90=E9=94=99=E8=AF=AF=E5=88=86?= =?UTF-8?q?=E5=89=B2=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/utils.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index 652dec4f9..47014d1c1 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -255,10 +255,11 @@ def split_into_sentences_w_remove_punctuation(text: str) -> List[str]: if not is_western_paragraph(text): # 当语言为中文时,统一将英文逗号转换为中文逗号 text = text.replace(',', ',') + text = text.replace('\n', ' ') else: # 用"|seg|"作为分割符分开 text = re.sub(r'([.!?]) +', r'\1\|seg\|', text) - text = text.replace('\n', ' ') + text = text.replace('\n', '\|seg\|') text, mapping = protect_kaomoji(text) # print(f"处理前的文本: {text}") @@ -312,10 +313,12 @@ def split_into_sentences_w_remove_punctuation(text: str) -> List[str]: sentences_done = [] for sentence in sentences: sentence = sentence.rstrip(',,') - if random.random() < split_strength * 0.5: - sentence = sentence.replace(',', '').replace(',', '') - elif random.random() < split_strength: - sentence = sentence.replace(',', ' ').replace(',', ' ') + # 西文字符句子不进行随机合并 + if not is_western_paragraph(current_sentence): + if random.random() < split_strength * 0.5: + sentence = sentence.replace(',', '').replace(',', '') + elif random.random() < split_strength: + sentence = sentence.replace(',', ' ').replace(',', ' ') sentences_done.append(sentence) logger.info(f"处理后的句子: {sentences_done}") From 65c26af25b4a436fe131fab9c0147ac46bf1616d Mon Sep 17 00:00:00 2001 From: dax <88696221+Dax233@users.noreply.github.com> Date: Wed, 19 Mar 2025 18:54:02 +0800 Subject: [PATCH 04/14] modified: src/plugins/chat/utils.py --- src/plugins/chat/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index 47014d1c1..8f2f006f7 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -355,7 +355,7 @@ def random_remove_punctuation(text: str) -> str: def process_llm_response(text: str) -> List[str]: # processed_response = process_text_with_typos(content) # 对西文字符段落的回复长度设置为汉字字符的两倍 - if len(text) > 100 and not is_western_paragraph(text) : + if len(text) > 100 and not is_western_paragraph(text) : logger.warning(f"回复过长 ({len(text)} 字符),返回默认回复") return ['懒得说'] elif len(text) > 200 : From 0c08151f92955600835f7ce409ebee7945d0fa86 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 20 Mar 2025 21:32:05 +0800 Subject: [PATCH 05/14] Delete results directory --- results/personality_result.json | 46 --------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 results/personality_result.json diff --git a/results/personality_result.json b/results/personality_result.json deleted file mode 100644 index 6424598b9..000000000 --- a/results/personality_result.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "final_scores": { - "开放性": 5.5, - "尽责性": 5.0, - "外向性": 6.0, - "宜人性": 1.5, - "神经质": 6.0 - }, - "scenarios": [ - { - "场景": "在团队项目中,你发现一个同事的工作质量明显低于预期,这可能会影响整个项目的进度。", - "评估维度": [ - "尽责性", - "宜人性" - ] - }, - { - "场景": "你被邀请参加一个完全陌生的社交活动,现场都是不认识的人。", - "评估维度": [ - "外向性", - "神经质" - ] - }, - { - "场景": "你的朋友向你推荐了一个新的艺术展览,但风格与你平时接触的完全不同。", - "评估维度": [ - "开放性", - "外向性" - ] - }, - { - "场景": "在工作中,你遇到了一个技术难题,需要学习全新的技术栈。", - "评估维度": [ - "开放性", - "尽责性" - ] - }, - { - "场景": "你的朋友因为个人原因情绪低落,向你寻求帮助。", - "评估维度": [ - "宜人性", - "神经质" - ] - } - ] -} \ No newline at end of file From 3b2c97b7bc1802a95024e9a59ce219266b883062 Mon Sep 17 00:00:00 2001 From: Charlie Wang Date: Thu, 20 Mar 2025 22:29:19 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86bot=E7=AD=89?= =?UTF-8?q?=E5=BE=85=E4=B8=80=E4=B8=AA=E4=BA=BA=E8=AF=B4=E5=AE=8C=E6=89=80?= =?UTF-8?q?=E6=9C=89=E8=AF=9D=E7=9A=84=E5=8A=9F=E8=83=BD=E5=92=8C=E9=80=9A?= =?UTF-8?q?=E8=BF=87at=E5=BF=AB=E9=80=9F=E5=A2=9E=E5=8A=A0=E5=A5=BD?= =?UTF-8?q?=E6=84=9F=E5=BA=A6=E7=9A=84=E5=8A=9F=E8=83=BD(sec/plugins/chat/?= =?UTF-8?q?bot.py)=20=E7=BB=99=E5=8F=AF=E8=A7=86=E5=8C=96=E6=8E=A8?= =?UTF-8?q?=E7=90=86/=E8=AE=B0=E5=BF=86=E5=8A=A0=E4=BA=86sys.path.insert?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E6=89=BE=E4=B8=8D=E5=88=B0src=20requirements?= =?UTF-8?q?=E5=8A=A0=E4=BA=86scipy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | Bin 658 -> 672 bytes src/gui/reasoning_gui.py | 2 + src/plugins/chat/bot.py | 49 ++++++++++++++++-- .../memory_system/memory_manual_build.py | 6 +++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1e9e5ff25b8c4ccae9904607247966efcd269ab7..0dfd751484930ec11fed6da3b69ff72e6f5be121 100644 GIT binary patch delta 22 dcmbQlx`1`VBqlyy1}=tThGd3Jh60941^_ None: + groupinfo = message.message_info.group_info + userinfo = message.message_info.user_info + messageinfo = message.message_info + # 过滤词 for word in global_config.ban_words: if word in message.processed_plain_text: @@ -120,7 +137,15 @@ class ChatBot: await self.storage.store_message(message, chat, topic[0] if topic else None) - is_mentioned = is_mentioned_bot_in_message(message) + is_mentioned = is_mentioned_bot_in_message(message) or groupID == -1 + if is_mentioned: + relationship_value = relationship_manager.get_relationship(chat).relationship_value if relationship_manager.get_relationship(chat) else 0.0 + await relationship_manager.update_relationship( + chat_stream=chat, + ) + await relationship_manager.update_relationship_value( + chat_stream=chat, relationship_value = min(max(40 - relationship_value, 2)/2, 10000) + ) reply_probability = await willing_manager.change_reply_willing_received( chat_stream=chat, is_mentioned_bot=is_mentioned, @@ -130,15 +155,27 @@ class ChatBot: sender_id=str(message.message_info.user_info.user_id), ) current_willing = willing_manager.get_willing(chat_stream=chat) - + actual_prob = random() logger.info( f"[{current_time}][{chat.group_info.group_name if chat.group_info else '私聊'}]{chat.user_info.user_nickname}:" f"{message.processed_plain_text}[回复意愿:{current_willing:.2f}][概率:{reply_probability * 100:.1f}%]" ) - + reply_probability = 1 if is_mentioned else reply_probability + logger.info("!!!决定回复!!!" if actual_prob < reply_probability else "===不理===") + response = None # 开始组织语言 - if random() < reply_probability: + if groupID not in self.group_message_dict: + self.group_message_dict[groupID] = {} + this_msg_time = time.time() + if userinfo.user_id not in self.group_message_dict[groupID].keys(): + self.group_message_dict[groupID][userinfo.user_id] = -1 + + if (actual_prob < reply_probability) or (self.group_message_dict[groupID][userinfo.user_id] != -1): + self.group_message_dict[groupID][userinfo.user_id] = this_msg_time + await asyncio.sleep(30) + if this_msg_time != self.group_message_dict[groupID][userinfo.user_id]: + return bot_user_info = UserInfo( user_id=global_config.BOT_QQ, user_nickname=global_config.BOT_NICKNAME, @@ -168,6 +205,8 @@ class ChatBot: # print(f"response: {response}") if response: # print(f"有response: {response}") + if this_msg_time == self.group_message_dict[groupID][userinfo.user_id]: + self.group_message_dict[groupID][userinfo.user_id] = -1 container = message_manager.get_container(chat.stream_id) thinking_message = None # 找到message,删除 diff --git a/src/plugins/memory_system/memory_manual_build.py b/src/plugins/memory_system/memory_manual_build.py index 9b01640a9..3b4b2af82 100644 --- a/src/plugins/memory_system/memory_manual_build.py +++ b/src/plugins/memory_system/memory_manual_build.py @@ -11,6 +11,12 @@ from pathlib import Path import matplotlib.pyplot as plt import networkx as nx from dotenv import load_dotenv +sys.path.insert(0, sys.path[0]+"/../") +sys.path.insert(0, sys.path[0]+"/../") +sys.path.insert(0, sys.path[0]+"/../") +sys.path.insert(0, sys.path[0]+"/../") +sys.path.insert(0, sys.path[0]+"/../") +print(sys.path) from src.common.logger import get_module_logger import jieba From dcf2b7c1ff5074a0ec41d279feb4305fd5f6be18 Mon Sep 17 00:00:00 2001 From: Charlie Wang Date: Fri, 21 Mar 2025 16:17:48 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=80=9D?= =?UTF-8?q?=E8=80=83=E8=BF=87=E7=A8=8B=E6=9C=AA=E8=83=BD=E5=A6=A5=E5=BD=93?= =?UTF-8?q?=E5=A4=84=E7=90=86(=E8=87=B3=E5=B0=91=E5=9C=A8=E7=81=AB?= =?UTF-8?q?=E5=B1=B1=E4=B8=8A=E6=98=AF=E8=BF=99=E6=A0=B7)=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/models/utils_model.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/models/utils_model.py b/src/plugins/models/utils_model.py index d915b3759..c8b358a4f 100644 --- a/src/plugins/models/utils_model.py +++ b/src/plugins/models/utils_model.py @@ -274,6 +274,7 @@ class LLM_request: raise RuntimeError(f"请求被拒绝: {error_code_mapping.get(response.status)}") response.raise_for_status() + reasoning_content = "" # 将流式输出转化为非流式输出 if stream_mode: @@ -303,6 +304,8 @@ class LLM_request: accumulated_content += delta_content # 检测流式输出文本是否结束 finish_reason = chunk["choices"][0].get("finish_reason") + if delta.get("reasoning_content", None): + reasoning_content += delta["reasoning_content"] if finish_reason == "stop": chunk_usage = chunk.get("usage", None) if chunk_usage: @@ -314,7 +317,6 @@ class LLM_request: except Exception as e: logger.exception(f"解析流式输出错误: {str(e)}") content = accumulated_content - reasoning_content = "" think_match = re.search(r"(.*?)", content, re.DOTALL) if think_match: reasoning_content = think_match.group(1).strip() From 842da395ea115b09f5def8a558b43f66c700e9d4 Mon Sep 17 00:00:00 2001 From: Charlie Wang Date: Fri, 21 Mar 2025 16:23:08 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E4=B8=8D=E4=BD=BF=E7=94=A8conda=E8=80=8C?= =?UTF-8?q?=E6=98=AF=E4=BD=BF=E7=94=A8venv=E5=90=AF=E5=8A=A8=E9=BA=A6?= =?UTF-8?q?=E9=BA=A6=E8=84=91=E5=86=85=E6=89=80=E6=83=B3gui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/run_thingking.bat | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/run_thingking.bat b/script/run_thingking.bat index a134da6fe..0806e46ed 100644 --- a/script/run_thingking.bat +++ b/script/run_thingking.bat @@ -1,5 +1,5 @@ -call conda activate niuniu -cd src\gui -start /b python reasoning_gui.py +@REM call conda activate niuniu +cd ../src\gui +start /b ../../venv/scripts/python.exe reasoning_gui.py exit From cafa5340085d04010260ac4bbae1c6fafff4ec6f Mon Sep 17 00:00:00 2001 From: Charlie Wang Date: Fri, 21 Mar 2025 17:42:03 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=92=A4=E9=94=80?= =?UTF-8?q?=E5=AF=B9bot.py=E7=9A=84=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/bot.py | 49 +++++------------------------------------ 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 29cafdd61..d30940f97 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -1,6 +1,5 @@ import re import time -import asyncio from random import random from nonebot.adapters.onebot.v11 import ( Bot, @@ -54,9 +53,7 @@ class ChatBot: self._started = False self.mood_manager = MoodManager.get_instance() # 获取情绪管理器单例 self.mood_manager.start_mood_update() # 启动情绪更新 - - self.group_message_dict = {} - + self.emoji_chance = 0.2 # 发送表情包的基础概率 # self.message_streams = MessageStreamContainer() @@ -99,20 +96,6 @@ class ChatBot: await relationship_manager.update_relationship_value(chat_stream=chat, relationship_value=0) await message.process() - - await relationship_manager.update_relationship( - chat_stream=chat, - ) - await relationship_manager.update_relationship_value( - chat_stream=chat, relationship_value=0 - ) - groupid = groupinfo.group_id if groupinfo is not None else -1 - await self.message_process_onto_group(message, chat, groupid) - - async def message_process_onto_group(self, message: MessageRecvCQ, chat, groupID: int) -> None: - groupinfo = message.message_info.group_info - userinfo = message.message_info.user_info - messageinfo = message.message_info # 过滤词 for word in global_config.ban_words: @@ -144,15 +127,7 @@ class ChatBot: await self.storage.store_message(message, chat, topic[0] if topic else None) - is_mentioned = is_mentioned_bot_in_message(message) or groupID == -1 - if is_mentioned: - relationship_value = relationship_manager.get_relationship(chat).relationship_value if relationship_manager.get_relationship(chat) else 0.0 - await relationship_manager.update_relationship( - chat_stream=chat, - ) - await relationship_manager.update_relationship_value( - chat_stream=chat, relationship_value = min(max(40 - relationship_value, 2)/2, 10000) - ) + is_mentioned = is_mentioned_bot_in_message(message) reply_probability = await willing_manager.change_reply_willing_received( chat_stream=chat, is_mentioned_bot=is_mentioned, @@ -162,28 +137,16 @@ class ChatBot: sender_id=str(message.message_info.user_info.user_id), ) current_willing = willing_manager.get_willing(chat_stream=chat) - actual_prob = random() + logger.info( f"[{current_time}][{chat.group_info.group_name if chat.group_info else '私聊'}]" f"{chat.user_info.user_nickname}:" f"{message.processed_plain_text}[回复意愿:{current_willing:.2f}][概率:{reply_probability * 100:.1f}%]" ) - reply_probability = 1 if is_mentioned else reply_probability - logger.info("!!!决定回复!!!" if actual_prob < reply_probability else "===不理===") - + response = None # 开始组织语言 - if groupID not in self.group_message_dict: - self.group_message_dict[groupID] = {} - this_msg_time = time.time() - if userinfo.user_id not in self.group_message_dict[groupID].keys(): - self.group_message_dict[groupID][userinfo.user_id] = -1 - - if (actual_prob < reply_probability) or (self.group_message_dict[groupID][userinfo.user_id] != -1): - self.group_message_dict[groupID][userinfo.user_id] = this_msg_time - await asyncio.sleep(30) - if this_msg_time != self.group_message_dict[groupID][userinfo.user_id]: - return + if random() < reply_probability: bot_user_info = UserInfo( user_id=global_config.BOT_QQ, user_nickname=global_config.BOT_NICKNAME, @@ -213,8 +176,6 @@ class ChatBot: # print(f"response: {response}") if response: # print(f"有response: {response}") - if this_msg_time == self.group_message_dict[groupID][userinfo.user_id]: - self.group_message_dict[groupID][userinfo.user_id] = -1 container = message_manager.get_container(chat.stream_id) thinking_message = None # 找到message,删除 From f94a4bcfc73ab1dc4d176cce5fb8f5c6f6a8f0cd Mon Sep 17 00:00:00 2001 From: enKl03b Date: Fri, 21 Mar 2025 21:27:16 +0800 Subject: [PATCH 10/14] =?UTF-8?q?doc:=E9=83=A8=E5=88=86=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + docs/fast_q_a.md | 178 ++++++++++++++++++++++- docs/linux_deploy_guide_for_beginners.md | 11 +- docs/manual_deploy_linux.md | 3 - docs/pic/compass_downloadguide.png | Bin 0 -> 15412 bytes 5 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 docs/pic/compass_downloadguide.png diff --git a/README.md b/README.md index 3ff2548d7..b005bc189 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,8 @@ MaiMBot是一个开源项目,我们非常欢迎你的参与。你的贡献, - [🐳 Docker部署指南](docs/docker_deploy.md) +- [🖥️群晖 NAS 部署指南](docs/synology_deploy.md) + ### 配置说明 - [🎀 新手配置指南](docs/installation_cute.md) - 通俗易懂的配置教程,适合初次使用的猫娘 diff --git a/docs/fast_q_a.md b/docs/fast_q_a.md index 92800bad2..abec69b40 100644 --- a/docs/fast_q_a.md +++ b/docs/fast_q_a.md @@ -38,6 +38,9 @@ ### MongoDB相关问题 - 我应该怎么清空bot内存储的表情包 ❓ +>需要先安装`MongoDB Compass`,[下载链接](https://www.mongodb.com/try/download/compass),软件支持`macOS、Windows、Ubuntu、Redhat`系统 +>以Windows为例,保持如图所示选项,点击`Download`即可,如果是其他系统,请在`Platform`中自行选择: +> >打开你的MongoDB Compass软件,你会在左上角看到这样的一个界面: > @@ -68,7 +71,9 @@ - 为什么我连接不上MongoDB服务器 ❓ >这个问题比较复杂,但是你可以按照下面的步骤检查,看看具体是什么问题 -> + + +>#### Windows > 1. 检查有没有把 mongod.exe 所在的目录添加到 path。 具体可参照 > >  [CSDN-windows10设置环境变量Path详细步骤](https://blog.csdn.net/flame_007/article/details/106401215) @@ -112,4 +117,173 @@ >MONGODB_HOST=127.0.0.1 >MONGODB_PORT=27017 #修改这里 >DATABASE_NAME=MegBot ->``` \ No newline at end of file +>``` + +
+Linux(点击展开) + +#### **1. 检查 MongoDB 服务是否运行** +- **命令**: + ```bash + systemctl status mongod # 检查服务状态(Ubuntu/Debian/CentOS 7+) + service mongod status # 旧版系统(如 CentOS 6) + ``` +- **可能结果**: + - 如果显示 `active (running)`,服务已启动。 + - 如果未运行,启动服务: + ```bash + sudo systemctl start mongod # 启动服务 + sudo systemctl enable mongod # 设置开机自启 + ``` + +--- + +#### **2. 检查 MongoDB 端口监听** +MongoDB 默认使用 **27017** 端口。 +- **检查端口是否被监听**: + ```bash + sudo ss -tulnp | grep 27017 + 或 + sudo netstat -tulnp | grep 27017 + ``` +- **预期结果**: + ```bash + tcp LISTEN 0 128 0.0.0.0:27017 0.0.0.0:* users:(("mongod",pid=123,fd=11)) + ``` + - 如果无输出,说明 MongoDB 未监听端口。 + + +--- +#### **3. 检查防火墙设置** +- **Ubuntu/Debian(UFW 防火墙)**: + ```bash + sudo ufw status # 查看防火墙状态 + sudo ufw allow 27017/tcp # 开放 27017 端口 + sudo ufw reload # 重新加载规则 + ``` +- **CentOS/RHEL(firewalld)**: + ```bash + sudo firewall-cmd --list-ports # 查看已开放端口 + sudo firewall-cmd --add-port=27017/tcp --permanent # 永久开放端口 + sudo firewall-cmd --reload # 重新加载 + ``` +- **云服务器用户注意**:检查云平台安全组规则,确保放行 27017 端口。 + +--- + +#### **4. 检查端口占用** +如果 MongoDB 服务无法监听端口,可能是其他进程占用了 `27017` 端口。 +- **检查端口占用进程**: + ```bash + sudo lsof -i :27017 # 查看占用 27017 端口的进程 + 或 + sudo ss -ltnp 'sport = :27017' # 使用 ss 过滤端口 + ``` +- **结果示例**: + ```bash + COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME + java 1234 root 12u IPv4 123456 0t0 TCP *:27017 (LISTEN) + ``` + - 输出会显示占用端口的 **进程名** 和 **PID**(此处 `PID=1234`)。 + +- **解决方案**: + 1. **终止占用进程**(谨慎操作!确保进程非关键): + ```bash + sudo kill 1234 # 正常终止进程 + sudo kill -9 1234 # 强制终止(若正常终止无效) + ``` + 2. **修改端口**: + 编辑麦麦目录里的`.env.dev`文件,修改端口号: + ```ini + MONGODB_HOST=127.0.0.1 + MONGODB_PORT=27017 #修改这里 + DATABASE_NAME=MegBot + ``` + + +##### **注意事项** +- 终止进程前,务必确认该进程非系统关键服务(如未知进程占用,建议先排查来源),如果你不知道这个进程是否关键,请更改端口使用。 + +
+ +
+macOS(点击展开) + +### **1. 检查 MongoDB 服务状态** +**问题原因**:MongoDB 服务未启动 +**操作步骤**: +```bash +# 查看 MongoDB 是否正在运行(Homebrew 安装的默认服务名) +brew services list | grep mongodb + +# 如果状态为 "stopped" 或 "error",手动启动 +brew services start mongodb-community@8.0 +``` +✅ **预期结果**:输出显示 `started` 或 `running` +❌ **失败处理**: +- 若报错 `unrecognized service`,可能未正确安装 MongoDB,建议[重新安装](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-os-x/#install-mongodb-community-edition)。 + +--- + +### **2. 检查端口是否被占用** +**问题原因**:其他程序占用了 MongoDB 的默认端口(`27017`),导致服务无法启动或连接 +**操作步骤**: +```bash +# 检查 27017 端口占用情况(需 sudo 权限查看完整信息) +sudo lsof -i :27017 + +# 或使用 netstat 快速检测 +netstat -an | grep 27017 +``` +✅ **预期结果**: +- 若无 MongoDB 运行,应无输出 +- 若 MongoDB 已启动,应显示 `mongod` 进程 + +❌ **发现端口被占用**: +#### **解决方案1:终止占用进程** +1. 从 `lsof` 输出中找到占用端口的 **PID**(进程号) +2. 强制终止该进程(谨慎操作!确保进程非关键): + ```bash + kill -9 PID # 替换 PID 为实际数字(例如 kill -9 12345) + ``` +3. 重新启动 MongoDB 服务: + ```bash + brew services start mongodb-community@8.0 + ``` + +#### **解决方案2:修改端口** + 编辑麦麦目录里的`.env.dev`文件,修改端口号: + ```ini + MONGODB_HOST=127.0.0.1 + MONGODB_PORT=27017 #修改这里 + DATABASE_NAME=MegBot + ``` + +--- + +### **3. 检查防火墙设置** +**问题原因**:macOS 防火墙阻止连接 +**操作步骤**: +1. 打开 **系统设置 > 隐私与安全性 > 防火墙** +2. 临时关闭防火墙测试连接 +3. 若需长期开放,添加 MongoDB 到防火墙允许列表(通过终端或 GUI)。 + + +--- +### **4. 重置 MongoDB 环境** +***仅在以上步骤都无效时使用*** +**适用场景**:配置混乱导致无法修复 +```bash +# 停止服务并删除数据 +brew services stop mongodb-community@8.0 +rm -rf /usr/local/var/mongodb + +# 重新初始化(确保目录权限) +sudo mkdir -p /usr/local/var/mongodb +sudo chown -R $(whoami) /usr/local/var/mongodb + +# 重新启动 +brew services start mongodb-community@8.0 +``` + +
\ No newline at end of file diff --git a/docs/linux_deploy_guide_for_beginners.md b/docs/linux_deploy_guide_for_beginners.md index 1f1b0899f..f254cf665 100644 --- a/docs/linux_deploy_guide_for_beginners.md +++ b/docs/linux_deploy_guide_for_beginners.md @@ -2,7 +2,7 @@ ## 事前准备 -为了能使麦麦不间断的运行,你需要一台一直开着的主机。 +为了能使麦麦不间断的运行,你需要一台一直开着的服务器。 ### 如果你想购买服务器 华为云、阿里云、腾讯云等等都是在国内可以选择的选择。 @@ -12,6 +12,8 @@ ### 如果你不想购买服务器 你可以准备一台可以一直开着的电脑/主机,只需要保证能够正常访问互联网即可 +**下文将统称它们为`服务器`** + 我们假设你已经有了一台Linux架构的服务器。举例使用的是Ubuntu24.04,其他的原理相似。 ## 0.我们就从零开始吧 @@ -120,6 +122,7 @@ sudo apt install python-is-python3 ``` ## 3.MongoDB的安装 +*如果你是参考[官方文档](https://www.mongodb.com/zh-cn/docs/manual/administration/install-on-linux/#std-label-install-mdb-community-edition-linux)进行安装的,可跳过此步* ``` bash cd /moi/mai @@ -156,6 +159,7 @@ sudo systemctl enable mongod curl -o napcat.sh https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh && sudo bash napcat.sh ``` 执行后,脚本会自动帮你部署好QQ及Napcat +*注:如果你已经手动安装了Napcat和QQ,可忽略此步* 成功的标志是输入``` napcat ```出来炫酷的彩虹色界面 @@ -225,7 +229,8 @@ bot └─ bot_config.toml ``` -你要会vim直接在终端里修改也行,不过也可以把它们下到本地改好再传上去: +你可以使用vim、nano等编辑器直接在终端里修改这些配置文件,但如果你不熟悉它们的操作,也可以使用带图形界面的编辑器。 +如果你的麦麦部署在远程服务器,也可以把它们下载到本地改好再传上去 ### step 5 文件配置 @@ -244,7 +249,7 @@ bot - [⚙️ 标准配置指南](./installation_standard.md) - 简明专业的配置说明,适合有经验的用户 -**step # 6** 运行 +### step 6 运行 现在再运行 diff --git a/docs/manual_deploy_linux.md b/docs/manual_deploy_linux.md index 653284bf5..5a8806771 100644 --- a/docs/manual_deploy_linux.md +++ b/docs/manual_deploy_linux.md @@ -24,9 +24,6 @@ --- -## 一键部署 -请下载并运行项目根目录中的run.sh并按照提示安装,部署完成后请参照后续配置指南进行配置 - ## 环境配置 ### 1️⃣ **确认Python版本** diff --git a/docs/pic/compass_downloadguide.png b/docs/pic/compass_downloadguide.png new file mode 100644 index 0000000000000000000000000000000000000000..06a08b52d0a19cdb0c0368315bbed6c595cb4af0 GIT binary patch literal 15412 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 Date: Sat, 22 Mar 2025 10:24:42 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E9=98=B2=E6=AD=A2=E6=97=A5=E7=A8=8B?= =?UTF-8?q?=E6=8A=A5=E9=94=99=E7=82=B8=E9=A3=9E=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/schedule/schedule_generator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/schedule/schedule_generator.py b/src/plugins/schedule/schedule_generator.py index d58211215..d6ba165ee 100644 --- a/src/plugins/schedule/schedule_generator.py +++ b/src/plugins/schedule/schedule_generator.py @@ -101,6 +101,9 @@ class ScheduleGenerator: except json.JSONDecodeError: logger.exception("解析日程失败: {}".format(schedule_text)) return False + except Exception as e: + logger.exception(f"解析日程发生错误:{str(e)}") + return False def _parse_time(self, time_str: str) -> str: """解析时间字符串,转换为时间""" @@ -158,7 +161,7 @@ class ScheduleGenerator: def print_schedule(self): """打印完整的日程安排""" if not self._parse_schedule(self.today_schedule_text): - logger.warning("今日日程有误,将在下次运行时重新生成") + logger.warning("今日日程有误,将在两小时后重新生成") db.schedule.delete_one({"date": datetime.datetime.now().strftime("%Y-%m-%d")}) else: logger.info("=== 今日日程安排 ===") From ab72ef855dcb2bb8d651cb8e8e7c7f9ea6623bef Mon Sep 17 00:00:00 2001 From: corolin Date: Fri, 21 Mar 2025 01:00:50 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9C=A8=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E5=88=B0=E6=8B=8D=E4=B8=80=E6=8B=8D=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E7=94=B1=E4=BA=8E=E6=9F=90=E4=BA=9B=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0=E6=97=A0=E6=B3=95=E8=8E=B7=E5=8F=96=E5=88=B0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=86=85=E5=AE=B9=E5=AF=BC=E8=87=B4=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=EF=BC=8C=E4=B8=94=E5=BD=B1=E5=93=8D=E5=88=B0=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=9A=84=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit ac466fbd366a98ba35b3c94880a67faaa95f781a) --- src/plugins/chat/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 38450f903..aebe1e7db 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -296,7 +296,7 @@ class ChatBot: return raw_message = f"[戳了戳]{global_config.BOT_NICKNAME}" # 默认类型 - if info := event.raw_info: + if info := event.model_extra["raw_info"]: poke_type = info[2].get("txt", "戳了戳") # 戳戳类型,例如“拍一拍”、“揉一揉”、“捏一捏” custom_poke_message = info[4].get("txt", "") # 自定义戳戳消息,若不存在会为空字符串 raw_message = f"[{poke_type}]{global_config.BOT_NICKNAME}{custom_poke_message}" From 34378adca941b0e3cd8ae1de84e574c55fed3676 Mon Sep 17 00:00:00 2001 From: HYY Date: Sat, 22 Mar 2025 17:40:42 +0800 Subject: [PATCH 13/14] =?UTF-8?q?fix:=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=AD=A3=E5=B8=B8=E5=8F=91=E9=80=81=E5=BF=83?= =?UTF-8?q?=E8=B7=B3=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/remote/remote.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/plugins/remote/remote.py b/src/plugins/remote/remote.py index 65d77cc2d..fdc805df1 100644 --- a/src/plugins/remote/remote.py +++ b/src/plugins/remote/remote.py @@ -79,22 +79,42 @@ class HeartbeatThread(threading.Thread): self.interval = interval self.client_id = get_unique_id() self.running = True + self.stop_event = threading.Event() # 添加事件对象用于可中断的等待 + self.last_heartbeat_time = 0 # 记录上次发送心跳的时间 def run(self): """线程运行函数""" logger.debug(f"心跳线程已启动,客户端ID: {self.client_id}") while self.running: + # 发送心跳 if send_heartbeat(self.server_url, self.client_id): logger.info(f"{self.interval}秒后发送下一次心跳...") else: logger.info(f"{self.interval}秒后重试...") - - time.sleep(self.interval) # 使用同步的睡眠 + + self.last_heartbeat_time = time.time() + + # 使用可中断的等待代替 sleep + # 每秒检查一次是否应该停止或发送心跳 + remaining_wait = self.interval + while remaining_wait > 0 and self.running: + # 每次最多等待1秒,便于及时响应停止请求 + wait_time = min(1, remaining_wait) + if self.stop_event.wait(wait_time): + break # 如果事件被设置,立即退出等待 + remaining_wait -= wait_time + + # 检查是否由于外部原因导致间隔异常延长 + if time.time() - self.last_heartbeat_time >= self.interval * 1.5: + logger.warning("检测到心跳间隔异常延长,立即发送心跳") + break def stop(self): """停止线程""" self.running = False + self.stop_event.set() # 设置事件,中断等待 + logger.debug("心跳线程已收到停止信号") def main(): From 4b6a315b8e93765f57b24b53c79925ccd7b37b2d Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sun, 23 Mar 2025 00:01:26 +0800 Subject: [PATCH 14/14] =?UTF-8?q?secret=20=E7=A5=9E=E7=A7=98=E5=B0=8F?= =?UTF-8?q?=E6=B5=8B=E9=AA=8C=E5=8A=A0=E5=BC=BA=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory_system/memory_manual_build.py | 1 - src/plugins/personality/can_i_recog_u.py | 351 ++++++++++++++++++ .../personality/renqingziji_with_mymy.py | 196 ++++++++++ src/plugins/personality/who_r_u.py | 155 ++++++++ src/plugins/willing/mode_classical.py | 4 +- 5 files changed, 704 insertions(+), 3 deletions(-) create mode 100644 src/plugins/personality/can_i_recog_u.py create mode 100644 src/plugins/personality/renqingziji_with_mymy.py create mode 100644 src/plugins/personality/who_r_u.py diff --git a/src/plugins/memory_system/memory_manual_build.py b/src/plugins/memory_system/memory_manual_build.py index 7e392668f..4b5d3b155 100644 --- a/src/plugins/memory_system/memory_manual_build.py +++ b/src/plugins/memory_system/memory_manual_build.py @@ -23,7 +23,6 @@ import jieba root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) sys.path.append(root_path) -from src.common.logger import get_module_logger # noqa: E402 from src.common.database import db # noqa E402 from src.plugins.memory_system.offline_llm import LLMModel # noqa E402 diff --git a/src/plugins/personality/can_i_recog_u.py b/src/plugins/personality/can_i_recog_u.py new file mode 100644 index 000000000..715c9ffa0 --- /dev/null +++ b/src/plugins/personality/can_i_recog_u.py @@ -0,0 +1,351 @@ +""" +基于聊天记录的人格特征分析系统 +""" + +from typing import Dict, List +import json +import os +from pathlib import Path +from dotenv import load_dotenv +import sys +import random +from collections import defaultdict +import matplotlib.pyplot as plt +import numpy as np +from datetime import datetime +import matplotlib.font_manager as fm + +current_dir = Path(__file__).resolve().parent +project_root = current_dir.parent.parent.parent +env_path = project_root / ".env.prod" + +root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) +sys.path.append(root_path) + +from src.plugins.personality.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa: E402 +from src.plugins.personality.questionnaire import FACTOR_DESCRIPTIONS # noqa: E402 +from src.plugins.personality.offline_llm import LLMModel # noqa: E402 +from src.plugins.personality.who_r_u import MessageAnalyzer # noqa: E402 + +# 加载环境变量 +if env_path.exists(): + print(f"从 {env_path} 加载环境变量") + load_dotenv(env_path) +else: + print(f"未找到环境变量文件: {env_path}") + print("将使用默认配置") + +class ChatBasedPersonalityEvaluator: + def __init__(self): + self.personality_traits = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0} + self.scenarios = [] + self.message_analyzer = MessageAnalyzer() + self.llm = LLMModel() + self.trait_scores_history = defaultdict(list) # 记录每个特质的得分历史 + + # 为每个人格特质获取对应的场景 + for trait in PERSONALITY_SCENES: + scenes = get_scene_by_factor(trait) + if not scenes: + continue + scene_keys = list(scenes.keys()) + selected_scenes = random.sample(scene_keys, min(3, len(scene_keys))) + + for scene_key in selected_scenes: + scene = scenes[scene_key] + other_traits = [t for t in PERSONALITY_SCENES if t != trait] + secondary_trait = random.choice(other_traits) + self.scenarios.append({ + "场景": scene["scenario"], + "评估维度": [trait, secondary_trait], + "场景编号": scene_key + }) + + def analyze_chat_context(self, messages: List[Dict]) -> str: + """ + 分析一组消息的上下文,生成场景描述 + """ + context = "" + for msg in messages: + nickname = msg.get('user_info', {}).get('user_nickname', '未知用户') + content = msg.get('processed_plain_text', msg.get('detailed_plain_text', '')) + if content: + context += f"{nickname}: {content}\n" + return context + + def evaluate_chat_response( + self, user_nickname: str, chat_context: str, dimensions: List[str] = None) -> Dict[str, float]: + """ + 评估聊天内容在各个人格维度上的得分 + """ + # 使用所有维度进行评估 + dimensions = list(self.personality_traits.keys()) + + dimension_descriptions = [] + for dim in dimensions: + desc = FACTOR_DESCRIPTIONS.get(dim, "") + if desc: + dimension_descriptions.append(f"- {dim}:{desc}") + + dimensions_text = "\n".join(dimension_descriptions) + + prompt = f"""请根据以下聊天记录,评估"{user_nickname}"在大五人格模型中的维度得分(1-6分)。 + +聊天记录: +{chat_context} + +需要评估的维度说明: +{dimensions_text} + +请按照以下格式输出评估结果,注意,你的评价对象是"{user_nickname}"(仅输出JSON格式): +{{ + "开放性": 分数, + "严谨性": 分数, + "外向性": 分数, + "宜人性": 分数, + "神经质": 分数 +}} + +评分标准: +1 = 非常不符合该维度特征 +2 = 比较不符合该维度特征 +3 = 有点不符合该维度特征 +4 = 有点符合该维度特征 +5 = 比较符合该维度特征 +6 = 非常符合该维度特征 + +如果你觉得某个维度没有相关信息或者无法判断,请输出0分 + +请根据聊天记录的内容和语气,结合维度说明进行评分。如果维度可以评分,确保分数在1-6之间。如果没有体现,请输出0分""" + + try: + ai_response, _ = self.llm.generate_response(prompt) + start_idx = ai_response.find("{") + end_idx = ai_response.rfind("}") + 1 + if start_idx != -1 and end_idx != 0: + json_str = ai_response[start_idx:end_idx] + scores = json.loads(json_str) + return {k: max(0, min(6, float(v))) for k, v in scores.items()} + else: + print("AI响应格式不正确,使用默认评分") + return {dim: 0 for dim in dimensions} + except Exception as e: + print(f"评估过程出错:{str(e)}") + return {dim: 0 for dim in dimensions} + + def evaluate_user_personality(self, qq_id: str, num_samples: int = 10, context_length: int = 5) -> Dict: + """ + 基于用户的聊天记录评估人格特征 + + Args: + qq_id (str): 用户QQ号 + num_samples (int): 要分析的聊天片段数量 + context_length (int): 每个聊天片段的上下文长度 + + Returns: + Dict: 评估结果 + """ + # 获取用户的随机消息及其上下文 + chat_contexts, user_nickname = self.message_analyzer.get_user_random_contexts( + qq_id, num_messages=num_samples, context_length=context_length) + if not chat_contexts: + return {"error": f"没有找到QQ号 {qq_id} 的消息记录"} + + # 初始化评分 + final_scores = defaultdict(float) + dimension_counts = defaultdict(int) + chat_samples = [] + + # 清空历史记录 + self.trait_scores_history.clear() + + # 分析每个聊天上下文 + for chat_context in chat_contexts: + # 评估这段聊天内容的所有维度 + scores = self.evaluate_chat_response(user_nickname, chat_context) + + # 记录样本 + chat_samples.append({ + "聊天内容": chat_context, + "评估维度": list(self.personality_traits.keys()), + "评分": scores + }) + + # 更新总分和历史记录 + for dimension, score in scores.items(): + if score > 0: # 只统计大于0的有效分数 + final_scores[dimension] += score + dimension_counts[dimension] += 1 + self.trait_scores_history[dimension].append(score) + + # 计算平均分 + average_scores = {} + for dimension in self.personality_traits: + if dimension_counts[dimension] > 0: + average_scores[dimension] = round(final_scores[dimension] / dimension_counts[dimension], 2) + else: + average_scores[dimension] = 0 # 如果没有有效分数,返回0 + + # 生成趋势图 + self._generate_trend_plot(qq_id, user_nickname) + + result = { + "用户QQ": qq_id, + "用户昵称": user_nickname, + "样本数量": len(chat_samples), + "人格特征评分": average_scores, + "维度评估次数": dict(dimension_counts), + "详细样本": chat_samples, + "特质得分历史": {k: v for k, v in self.trait_scores_history.items()} + } + + # 保存结果 + os.makedirs("results", exist_ok=True) + result_file = f"results/personality_result_{qq_id}.json" + with open(result_file, "w", encoding="utf-8") as f: + json.dump(result, f, ensure_ascii=False, indent=2) + + return result + + def _generate_trend_plot(self, qq_id: str, user_nickname: str): + """ + 生成人格特质累计平均分变化趋势图 + """ + # 查找系统中可用的中文字体 + chinese_fonts = [] + for f in fm.fontManager.ttflist: + try: + if '简' in f.name or 'SC' in f.name or '黑' in f.name or '宋' in f.name or '微软' in f.name: + chinese_fonts.append(f.name) + except Exception: + continue + + if chinese_fonts: + plt.rcParams['font.sans-serif'] = chinese_fonts + ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS'] + else: + # 如果没有找到中文字体,使用默认字体,并将中文昵称转换为拼音或英文 + try: + from pypinyin import lazy_pinyin + user_nickname = ''.join(lazy_pinyin(user_nickname)) + except ImportError: + user_nickname = "User" # 如果无法转换为拼音,使用默认英文 + + plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 + + plt.figure(figsize=(12, 6)) + plt.style.use('bmh') # 使用内置的bmh样式,它有类似seaborn的美观效果 + + colors = { + "开放性": "#FF9999", + "严谨性": "#66B2FF", + "外向性": "#99FF99", + "宜人性": "#FFCC99", + "神经质": "#FF99CC" + } + + # 计算每个维度在每个时间点的累计平均分 + cumulative_averages = {} + for trait, scores in self.trait_scores_history.items(): + if not scores: + continue + + averages = [] + total = 0 + valid_count = 0 + for score in scores: + if score > 0: # 只计算大于0的有效分数 + total += score + valid_count += 1 + if valid_count > 0: + averages.append(total / valid_count) + else: + # 如果当前分数无效,使用前一个有效的平均分 + if averages: + averages.append(averages[-1]) + else: + continue # 跳过无效分数 + + if averages: # 只有在有有效分数的情况下才添加到累计平均中 + cumulative_averages[trait] = averages + + # 绘制每个维度的累计平均分变化趋势 + for trait, averages in cumulative_averages.items(): + x = range(1, len(averages) + 1) + plt.plot(x, averages, 'o-', label=trait, color=colors.get(trait), linewidth=2, markersize=8) + + # 添加趋势线 + z = np.polyfit(x, averages, 1) + p = np.poly1d(z) + plt.plot(x, p(x), '--', color=colors.get(trait), alpha=0.5) + + plt.title(f"{user_nickname} 的人格特质累计平均分变化趋势", fontsize=14, pad=20) + plt.xlabel("评估次数", fontsize=12) + plt.ylabel("累计平均分", fontsize=12) + plt.grid(True, linestyle='--', alpha=0.7) + plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) + plt.ylim(0, 7) + plt.tight_layout() + + # 保存图表 + os.makedirs("results/plots", exist_ok=True) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + plot_file = f"results/plots/personality_trend_{qq_id}_{timestamp}.png" + plt.savefig(plot_file, dpi=300, bbox_inches='tight') + plt.close() + +def analyze_user_personality(qq_id: str, num_samples: int = 10, context_length: int = 5) -> str: + """ + 分析用户人格特征的便捷函数 + + Args: + qq_id (str): 用户QQ号 + num_samples (int): 要分析的聊天片段数量 + context_length (int): 每个聊天片段的上下文长度 + + Returns: + str: 格式化的分析结果 + """ + evaluator = ChatBasedPersonalityEvaluator() + result = evaluator.evaluate_user_personality(qq_id, num_samples, context_length) + + if "error" in result: + return result["error"] + + # 格式化输出 + output = f"QQ号 {qq_id} ({result['用户昵称']}) 的人格特征分析结果:\n" + output += "=" * 50 + "\n\n" + + output += "人格特征评分:\n" + for trait, score in result["人格特征评分"].items(): + if score == 0: + output += f"{trait}: 数据不足,无法判断 (评估次数: {result['维度评估次数'].get(trait, 0)})\n" + else: + output += f"{trait}: {score}/6 (评估次数: {result['维度评估次数'].get(trait, 0)})\n" + + # 添加变化趋势描述 + if trait in result["特质得分历史"] and len(result["特质得分历史"][trait]) > 1: + scores = [s for s in result["特质得分历史"][trait] if s != 0] # 过滤掉无效分数 + if len(scores) > 1: # 确保有足够的有效分数计算趋势 + trend = np.polyfit(range(len(scores)), scores, 1)[0] + if abs(trend) < 0.1: + trend_desc = "保持稳定" + elif trend > 0: + trend_desc = "呈上升趋势" + else: + trend_desc = "呈下降趋势" + output += f" 变化趋势: {trend_desc} (斜率: {trend:.2f})\n" + + output += f"\n分析样本数量:{result['样本数量']}\n" + output += f"结果已保存至:results/personality_result_{qq_id}.json\n" + output += "变化趋势图已保存至:results/plots/目录\n" + + return output + +if __name__ == "__main__": + # 测试代码 + # test_qq = "" # 替换为要测试的QQ号 + # print(analyze_user_personality(test_qq, num_samples=30, context_length=20)) + # test_qq = "" + # print(analyze_user_personality(test_qq, num_samples=30, context_length=20)) + test_qq = "1026294844" + print(analyze_user_personality(test_qq, num_samples=30, context_length=30)) diff --git a/src/plugins/personality/renqingziji_with_mymy.py b/src/plugins/personality/renqingziji_with_mymy.py new file mode 100644 index 000000000..511395e51 --- /dev/null +++ b/src/plugins/personality/renqingziji_with_mymy.py @@ -0,0 +1,196 @@ +""" +The definition of artificial personality in this paper follows the dispositional para-digm and adapts a definition of +personality developed for humans [17]: +Personality for a human is the "whole and organisation of relatively stable tendencies and patterns of experience and +behaviour within one person (distinguishing it from other persons)". This definition is modified for artificial +personality: +Artificial personality describes the relatively stable tendencies and patterns of behav-iour of an AI-based machine that +can be designed by developers and designers via different modalities, such as language, creating the impression +of individuality of a humanized social agent when users interact with the machine.""" + +from typing import Dict, List +import json +import os +from pathlib import Path +from dotenv import load_dotenv +import sys + +""" +第一种方案:基于情景评估的人格测定 +""" +current_dir = Path(__file__).resolve().parent +project_root = current_dir.parent.parent.parent +env_path = project_root / ".env.prod" + +root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) +sys.path.append(root_path) + +from src.plugins.personality.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa: E402 +from src.plugins.personality.questionnaire import FACTOR_DESCRIPTIONS # noqa: E402 +from src.plugins.personality.offline_llm import LLMModel # noqa: E402 + +# 加载环境变量 +if env_path.exists(): + print(f"从 {env_path} 加载环境变量") + load_dotenv(env_path) +else: + print(f"未找到环境变量文件: {env_path}") + print("将使用默认配置") + + +class PersonalityEvaluator_direct: + def __init__(self): + self.personality_traits = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0} + self.scenarios = [] + + # 为每个人格特质获取对应的场景 + for trait in PERSONALITY_SCENES: + scenes = get_scene_by_factor(trait) + if not scenes: + continue + + # 从每个维度选择3个场景 + import random + + scene_keys = list(scenes.keys()) + selected_scenes = random.sample(scene_keys, min(3, len(scene_keys))) + + for scene_key in selected_scenes: + scene = scenes[scene_key] + + # 为每个场景添加评估维度 + # 主维度是当前特质,次维度随机选择一个其他特质 + other_traits = [t for t in PERSONALITY_SCENES if t != trait] + secondary_trait = random.choice(other_traits) + + self.scenarios.append( + {"场景": scene["scenario"], "评估维度": [trait, secondary_trait], "场景编号": scene_key} + ) + + self.llm = LLMModel() + + def evaluate_response(self, scenario: str, response: str, dimensions: List[str]) -> Dict[str, float]: + """ + 使用 DeepSeek AI 评估用户对特定场景的反应 + """ + # 构建维度描述 + dimension_descriptions = [] + for dim in dimensions: + desc = FACTOR_DESCRIPTIONS.get(dim, "") + if desc: + dimension_descriptions.append(f"- {dim}:{desc}") + + dimensions_text = "\n".join(dimension_descriptions) + + + prompt = f"""请根据以下场景和用户描述,评估用户在大五人格模型中的相关维度得分(1-6分)。 + +场景描述: +{scenario} + +用户回应: +{response} + +需要评估的维度说明: +{dimensions_text} + +请按照以下格式输出评估结果(仅输出JSON格式): +{{ + "{dimensions[0]}": 分数, + "{dimensions[1]}": 分数 +}} + +评分标准: +1 = 非常不符合该维度特征 +2 = 比较不符合该维度特征 +3 = 有点不符合该维度特征 +4 = 有点符合该维度特征 +5 = 比较符合该维度特征 +6 = 非常符合该维度特征 + +请根据用户的回应,结合场景和维度说明进行评分。确保分数在1-6之间,并给出合理的评估。""" + + try: + ai_response, _ = self.llm.generate_response(prompt) + # 尝试从AI响应中提取JSON部分 + start_idx = ai_response.find("{") + end_idx = ai_response.rfind("}") + 1 + if start_idx != -1 and end_idx != 0: + json_str = ai_response[start_idx:end_idx] + scores = json.loads(json_str) + # 确保所有分数在1-6之间 + return {k: max(1, min(6, float(v))) for k, v in scores.items()} + else: + print("AI响应格式不正确,使用默认评分") + return {dim: 3.5 for dim in dimensions} + except Exception as e: + print(f"评估过程出错:{str(e)}") + return {dim: 3.5 for dim in dimensions} + + +def main(): + print("欢迎使用人格形象创建程序!") + print("接下来,您将面对一系列场景(共15个)。请根据您想要创建的角色形象,描述在该场景下可能的反应。") + print("每个场景都会评估不同的人格维度,最终得出完整的人格特征评估。") + print("评分标准:1=非常不符合,2=比较不符合,3=有点不符合,4=有点符合,5=比较符合,6=非常符合") + print("\n准备好了吗?按回车键开始...") + input() + + evaluator = PersonalityEvaluator_direct() + final_scores = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0} + dimension_counts = {trait: 0 for trait in final_scores.keys()} + + for i, scenario_data in enumerate(evaluator.scenarios, 1): + print(f"\n场景 {i}/{len(evaluator.scenarios)} - {scenario_data['场景编号']}:") + print("-" * 50) + print(scenario_data["场景"]) + print("\n请描述您的角色在这种情况下会如何反应:") + response = input().strip() + + if not response: + print("反应描述不能为空!") + continue + + print("\n正在评估您的描述...") + scores = evaluator.evaluate_response(scenario_data["场景"], response, scenario_data["评估维度"]) + + # 更新最终分数 + for dimension, score in scores.items(): + final_scores[dimension] += score + dimension_counts[dimension] += 1 + + print("\n当前评估结果:") + print("-" * 30) + for dimension, score in scores.items(): + print(f"{dimension}: {score}/6") + + if i < len(evaluator.scenarios): + print("\n按回车键继续下一个场景...") + input() + + # 计算平均分 + for dimension in final_scores: + if dimension_counts[dimension] > 0: + final_scores[dimension] = round(final_scores[dimension] / dimension_counts[dimension], 2) + + print("\n最终人格特征评估结果:") + print("-" * 30) + for trait, score in final_scores.items(): + print(f"{trait}: {score}/6") + print(f"测试场景数:{dimension_counts[trait]}") + + # 保存结果 + result = {"final_scores": final_scores, "dimension_counts": dimension_counts, "scenarios": evaluator.scenarios} + + # 确保目录存在 + os.makedirs("results", exist_ok=True) + + # 保存到文件 + with open("results/personality_result.json", "w", encoding="utf-8") as f: + json.dump(result, f, ensure_ascii=False, indent=2) + + print("\n结果已保存到 results/personality_result.json") + + +if __name__ == "__main__": + main() diff --git a/src/plugins/personality/who_r_u.py b/src/plugins/personality/who_r_u.py new file mode 100644 index 000000000..5ea502b82 --- /dev/null +++ b/src/plugins/personality/who_r_u.py @@ -0,0 +1,155 @@ +import random +import os +import sys +from pathlib import Path +import datetime +from typing import List, Dict, Optional + +current_dir = Path(__file__).resolve().parent +project_root = current_dir.parent.parent.parent +env_path = project_root / ".env.prod" + +root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) +sys.path.append(root_path) + +from src.common.database import db # noqa: E402 + +class MessageAnalyzer: + def __init__(self): + self.messages_collection = db["messages"] + + def get_message_context(self, message_id: int, context_length: int = 5) -> Optional[List[Dict]]: + """ + 获取指定消息ID的上下文消息列表 + + Args: + message_id (int): 消息ID + context_length (int): 上下文长度(单侧,总长度为 2*context_length + 1) + + Returns: + Optional[List[Dict]]: 消息列表,如果未找到则返回None + """ + # 从数据库获取指定消息 + target_message = self.messages_collection.find_one({"message_id": message_id}) + if not target_message: + return None + + # 获取该消息的stream_id + stream_id = target_message.get('chat_info', {}).get('stream_id') + if not stream_id: + return None + + # 获取同一stream_id的所有消息 + stream_messages = list(self.messages_collection.find({ + "chat_info.stream_id": stream_id + }).sort("time", 1)) + + # 找到目标消息在列表中的位置 + target_index = None + for i, msg in enumerate(stream_messages): + if msg['message_id'] == message_id: + target_index = i + break + + if target_index is None: + return None + + # 获取目标消息前后的消息 + start_index = max(0, target_index - context_length) + end_index = min(len(stream_messages), target_index + context_length + 1) + + return stream_messages[start_index:end_index] + + def format_messages(self, messages: List[Dict], target_message_id: Optional[int] = None) -> str: + """ + 格式化消息列表为可读字符串 + + Args: + messages (List[Dict]): 消息列表 + target_message_id (Optional[int]): 目标消息ID,用于标记 + + Returns: + str: 格式化的消息字符串 + """ + if not messages: + return "没有消息记录" + + reply = "" + for msg in messages: + # 消息时间 + msg_time = datetime.datetime.fromtimestamp(int(msg['time'])).strftime("%Y-%m-%d %H:%M:%S") + + # 获取消息内容 + message_text = msg.get('processed_plain_text', msg.get('detailed_plain_text', '无消息内容')) + nickname = msg.get('user_info', {}).get('user_nickname', '未知用户') + + # 标记当前消息 + is_target = "→ " if target_message_id and msg['message_id'] == target_message_id else " " + + reply += f"{is_target}[{msg_time}] {nickname}: {message_text}\n" + + if target_message_id and msg['message_id'] == target_message_id: + reply += " " + "-" * 50 + "\n" + + return reply + + def get_user_random_contexts( + self, qq_id: str, num_messages: int = 10, context_length: int = 5) -> tuple[List[str], str]: # noqa: E501 + """ + 获取用户的随机消息及其上下文 + + Args: + qq_id (str): QQ号 + num_messages (int): 要获取的随机消息数量 + context_length (int): 每条消息的上下文长度(单侧) + + Returns: + tuple[List[str], str]: (每个消息上下文的格式化字符串列表, 用户昵称) + """ + if not qq_id: + return [], "" + + # 获取用户所有消息 + all_messages = list(self.messages_collection.find({"user_info.user_id": int(qq_id)})) + if not all_messages: + return [], "" + + # 获取用户昵称 + user_nickname = all_messages[0].get('chat_info', {}).get('user_info', {}).get('user_nickname', '未知用户') + + # 随机选择指定数量的消息 + selected_messages = random.sample(all_messages, min(num_messages, len(all_messages))) + # 按时间排序 + selected_messages.sort(key=lambda x: int(x['time'])) + + # 存储所有上下文消息 + context_list = [] + + # 获取每条消息的上下文 + for msg in selected_messages: + message_id = msg['message_id'] + + # 获取消息上下文 + context_messages = self.get_message_context(message_id, context_length) + if context_messages: + formatted_context = self.format_messages(context_messages, message_id) + context_list.append(formatted_context) + + return context_list, user_nickname + +if __name__ == "__main__": + # 测试代码 + analyzer = MessageAnalyzer() + test_qq = "1026294844" # 替换为要测试的QQ号 + print(f"测试QQ号: {test_qq}") + print("-" * 50) + # 获取5条消息,每条消息前后各3条上下文 + contexts, nickname = analyzer.get_user_random_contexts(test_qq, num_messages=5, context_length=3) + + print(f"用户昵称: {nickname}\n") + # 打印每个上下文 + for i, context in enumerate(contexts, 1): + print(f"\n随机消息 {i}/{len(contexts)}:") + print("-" * 30) + print(context) + print("=" * 50) diff --git a/src/plugins/willing/mode_classical.py b/src/plugins/willing/mode_classical.py index 75237a525..0f32c0c75 100644 --- a/src/plugins/willing/mode_classical.py +++ b/src/plugins/willing/mode_classical.py @@ -41,8 +41,8 @@ class WillingManager: interested_rate = interested_rate * config.response_interested_rate_amplifier - if interested_rate > 0.5: - current_willing += interested_rate - 0.5 + if interested_rate > 0.4: + current_willing += interested_rate - 0.3 if is_mentioned_bot and current_willing < 1.0: current_willing += 1