diff --git a/.dockerignore b/.dockerignore index 0ed9090f..6c2d0773 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,4 +3,6 @@ __pycache__ *.pyc *.pyo *.pyd -.DS_Store \ No newline at end of file +.DS_Store +mongodb +napcat \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7eba6537..4e1606a5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ __pycache__/ *.py[cod] *$py.class llm_statistics.txt +mongodb +napcat +run_dev.bat # C extensions *.so diff --git a/README.md b/README.md index 04cfc077..fdfd10d0 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ > - 由于持续迭代,可能存在一些已知或未知的bug > - 由于开发中,可能消耗较多token -**交流群**: 766798517(仅用于开发和建议相关讨论)不一定有空回复,但大家可以自行交流部署问题,我会优先写文档和代码 +**交流群**: 766798517 一群人较多,建议加下面的(开发和建议相关讨论)不一定有空回复,会优先写文档和代码 +**交流群**: 571780722 另一个群(开发和建议相关讨论)不一定有空回复,会优先写文档和代码 +**交流群**: 1035228475 另一个群(开发和建议相关讨论)不一定有空回复,会优先写文档和代码 ##
@@ -45,11 +47,13 @@ ### 部署方式 -如果你不知道Docker是什么,建议寻找相关教程或使用手动部署 +如果你不知道Docker是什么,建议寻找相关教程或使用手动部署(现在不建议使用docker,更新慢,可能不适配) - [🐳 Docker部署指南](docs/docker_deploy.md) -- [📦 手动部署指南](docs/manual_deploy.md) +- [📦 手动部署指南 Windows](docs/manual_deploy_windows.md) + +- [📦 手动部署指南 Linux](docs/manual_deploy_linux.md) ### 配置说明 - [🎀 新手配置指南](docs/installation_cute.md) - 通俗易懂的配置教程,适合初次使用的猫娘 @@ -146,4 +150,4 @@ SengokuCola纯编程外行,面向cursor编程,很多代码史一样多多包 ## Stargazers over time -[![Stargazers over time](https://starchart.cc/SengokuCola/MaiMBot.svg?variant=adaptive)](https://starchart.cc/SengokuCola/MaiMBot) \ No newline at end of file +[![Stargazers over time](https://starchart.cc/SengokuCola/MaiMBot.svg?variant=adaptive)](https://starchart.cc/SengokuCola/MaiMBot) diff --git a/bot.py b/bot.py index 9503caa7..f2cc9164 100644 --- a/bot.py +++ b/bot.py @@ -1,9 +1,9 @@ import os import shutil import nonebot -from nonebot.adapters.onebot.v11 import Adapter from dotenv import load_dotenv from loguru import logger +from nonebot.adapters.onebot.v11 import Adapter # 获取没有加载env时的环境变量 env_mask = {key: os.getenv(key) for key in os.environ} diff --git a/docs/manual_deploy_linux.md b/docs/manual_deploy_linux.md new file mode 100644 index 00000000..09b2cfd0 --- /dev/null +++ b/docs/manual_deploy_linux.md @@ -0,0 +1,116 @@ +# 📦 Linux系统如何手动部署MaiMbot麦麦? + +## 准备工作 +- 一台联网的Linux设备(本教程以Ubuntu/Debian系为例) +- QQ小号(QQ框架的使用可能导致qq被风控,严重(小概率)可能会导致账号封禁,强烈不推荐使用大号) +- 可用的大模型API +- 一个AI助手,网上随便搜一家打开来用都行,可以帮你解决一些不懂的问题 +- 以下内容假设你对Linux系统有一定的了解,如果觉得难以理解,请直接用Windows系统部署[Windows系统部署指南](./manual_deploy_windows.md) + +## 你需要知道什么? + +- 如何正确向AI助手提问,来学习新知识 + +- Python是什么 + +- Python的虚拟环境是什么?如何创建虚拟环境 + +- 命令行是什么 + +- 数据库是什么?如何安装并启动MongoDB + +- 如何运行一个QQ机器人,以及NapCat框架是什么 +--- + +## 环境配置 + +### 1️⃣ **确认Python版本** + +需确保Python版本为3.9及以上 + +```bash +python --version +# 或 +python3 --version +``` +如果版本低于3.9,请更新Python版本。 +```bash +# Ubuntu/Debian +sudo apt update +sudo apt install python3.9 +# 如执行了这一步,建议在执行时将python3指向python3.9 +# 更新替代方案,设置 python3.9 为默认的 python3 版本: +sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 1 +sudo update-alternatives --config python3 +``` + +### 2️⃣ **创建虚拟环境** +```bash +# 方法1:使用venv(推荐) +python3 -m venv maimbot +source maimbot/bin/activate # 激活环境 + +# 方法2:使用conda(需先安装Miniconda) +wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh +bash Miniconda3-latest-Linux-x86_64.sh +conda create -n maimbot python=3.9 +conda activate maimbot + +# 通过以上方法创建并进入虚拟环境后,再执行以下命令 + +# 安装依赖(任选一种环境) +pip install -r requirements.txt +``` + +--- + +## 数据库配置 +### 3️⃣ **安装并启动MongoDB** +- 安装与启动:Debian参考[官方文档](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-debian/),Ubuntu参考[官方文档](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/) + +- 默认连接本地27017端口 +--- + +## NapCat配置 +### 4️⃣ **安装NapCat框架** + +- 参考[NapCat官方文档](https://www.napcat.wiki/guide/boot/Shell#napcat-installer-linux%E4%B8%80%E9%94%AE%E4%BD%BF%E7%94%A8%E8%84%9A%E6%9C%AC-%E6%94%AF%E6%8C%81ubuntu-20-debian-10-centos9)安装 + +- 使用QQ小号登录,添加反向WS地址: +`ws://localhost:8080/onebot/v11/ws` + +--- + +## 配置文件设置 +### 5️⃣ **配置文件设置,让麦麦Bot正常工作** +- 修改环境配置文件:`.env.prod` +- 修改机器人配置文件:`bot_config.toml` + + +--- + +## 启动机器人 +### 6️⃣ **启动麦麦机器人** +```bash +# 在项目目录下操作 +nb run +# 或 +python3 bot.py +``` + +--- + +## **其他组件(可选)** +- 直接运行 knowledge.py生成知识库 + + +--- + +## 常见问题 +🔧 权限问题:在命令前加`sudo` +🔌 端口占用:使用`sudo lsof -i :8080`查看端口占用 +🛡️ 防火墙:确保8080/27017端口开放 +```bash +sudo ufw allow 8080/tcp +sudo ufw allow 27017/tcp +``` \ No newline at end of file diff --git a/docs/manual_deploy.md b/docs/manual_deploy_windows.md similarity index 98% rename from docs/manual_deploy.md rename to docs/manual_deploy_windows.md index 6d53beb4..bd9c26f8 100644 --- a/docs/manual_deploy.md +++ b/docs/manual_deploy_windows.md @@ -1,4 +1,4 @@ -# 📦 如何手动部署MaiMbot麦麦? +# 📦 Windows系统如何手动部署MaiMbot麦麦? ## 你需要什么? diff --git a/flagged/log.csv b/flagged/log.csv deleted file mode 100644 index daeef4a9..00000000 --- a/flagged/log.csv +++ /dev/null @@ -1,2 +0,0 @@ -输入消息,推理内容,flag,username,timestamp -显示内容,,,,2025-02-18 16:50:53.643238 diff --git a/pyproject.toml b/pyproject.toml index 4f06cd5a..e54dcdac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,4 +5,19 @@ description = "New Bot Project" [tool.nonebot] plugins = ["src.plugins.chat"] -plugin_dirs = ["src/plugins"] \ No newline at end of file +plugin_dirs = ["src/plugins"] + +[tool.ruff] +# 设置 Python 版本 +target-version = "py39" + +# 启用的规则 +select = [ + "E", # pycodestyle 错误 + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear +] + +# 行长度设置 +line-length = 88 \ No newline at end of file diff --git a/run.bat b/run.bat new file mode 100644 index 00000000..1d138567 --- /dev/null +++ b/run.bat @@ -0,0 +1,6 @@ +@ECHO OFF +chcp 65001 +REM python -m venv venv +call venv\Scripts\activate.bat +REM pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple --upgrade -r requirements.txt +python run.py \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 00000000..0a195544 --- /dev/null +++ b/run.py @@ -0,0 +1,122 @@ +import os +import subprocess +import zipfile + +import requests +from tqdm import tqdm + + +def extract_files(zip_path, target_dir): + """ + 解压 + + Args: + zip_path: 源ZIP压缩包路径(需确保是有效压缩包) + target_dir: 目标文件夹路径(会自动创建不存在的目录) + """ + # 打开ZIP压缩包(上下文管理器自动处理关闭) + with zipfile.ZipFile(zip_path) as zip_ref: + # 通过第一个文件路径推断顶层目录名(格式如:top_dir/) + top_dir = zip_ref.namelist()[0].split("/")[0] + "/" + + # 遍历压缩包内所有文件条目 + for file in zip_ref.namelist(): + # 跳过目录条目,仅处理文件 + if file.startswith(top_dir) and not file.endswith("/"): + # 截取顶层目录后的相对路径(如:sub_dir/file.txt) + rel_path = file[len(top_dir) :] + + # 创建目标目录结构(含多级目录) + os.makedirs( + os.path.dirname(f"{target_dir}/{rel_path}"), + exist_ok=True, # 忽略已存在目录的错误 + ) + + # 读取压缩包内文件内容并写入目标路径 + with open(f"{target_dir}/{rel_path}", "wb") as f: + f.write(zip_ref.read(file)) + + +def run_cmd(command: str, open_new_window: bool = False): + """ + 运行 cmd 命令 + + Args: + command (str): 指定要运行的命令 + open_new_window (bool): 指定是否新建一个 cmd 窗口运行 + """ + creationflags = 0 + if open_new_window: + creationflags = subprocess.CREATE_NEW_CONSOLE + subprocess.Popen( + [ + "cmd.exe", + "/c", + command, + ], + creationflags=creationflags, + ) + + +def run_maimbot(): + run_cmd(r"napcat\NapCatWinBootMain.exe 10001", False) + run_cmd( + r"mongodb\bin\mongod.exe --dbpath=" + os.getcwd() + r"\mongodb\db --port 27017", + True, + ) + run_cmd("nb run", True) + + +def install_mongodb(): + """ + 安装 MongoDB + """ + print("下载 MongoDB") + resp = requests.get( + "https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-latest.zip", + stream=True, + ) + total = int(resp.headers.get("content-length", 0)) # 计算文件大小 + with open("mongodb.zip", "w+b") as file, tqdm( # 展示下载进度条,并解压文件 + desc="mongodb.zip", + total=total, + unit="iB", + unit_scale=True, + unit_divisor=1024, + ) as bar: + for data in resp.iter_content(chunk_size=1024): + size = file.write(data) + bar.update(size) + extract_files("mongodb.zip", "mongodb") + print("MongoDB 下载完成") + os.remove("mongodb.zip") + + +def install_napcat(): + run_cmd("start https://github.com/NapNeko/NapCatQQ/releases", True) + print("请检查弹出的浏览器窗口,点击**第一个**蓝色的“Win64无头” 下载 napcat") + napcat_filename = input( + "下载完成后请把文件复制到此文件夹,并将**不包含后缀的文件名**输入至此窗口,如 NapCat.32793.Shell:" + ) + extract_files(napcat_filename + ".zip", "napcat") + print("NapCat 安装完成") + os.remove(napcat_filename + ".zip") + + +if __name__ == "__main__": + os.system("cls") + choice = input( + "请输入要进行的操作:\n" + "1.首次安装\n" + "2.运行麦麦\n" + "3.运行麦麦并启动可视化推理界面\n" + ) + os.system("cls") + if choice == "1": + install_napcat() + install_mongodb() + elif choice == "2": + run_maimbot() + elif choice == "3": + run_maimbot() + run_cmd("python src/gui/reasoning_gui.py", True) diff --git a/run_db.bat b/script/run_db.bat similarity index 100% rename from run_db.bat rename to script/run_db.bat diff --git a/run_maimai.bat b/script/run_maimai.bat similarity index 100% rename from run_maimai.bat rename to script/run_maimai.bat diff --git a/run_thingking.bat b/script/run_thingking.bat similarity index 100% rename from run_thingking.bat rename to script/run_thingking.bat diff --git a/run_windows.bat b/script/run_windows.bat similarity index 100% rename from run_windows.bat rename to script/run_windows.bat diff --git a/setup.py b/setup.py index a6152a97..2598a38a 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import find_packages, setup setup( name="maimai-bot", diff --git a/src/common/database.py b/src/common/database.py index 5928abc4..45ac05da 100644 --- a/src/common/database.py +++ b/src/common/database.py @@ -1,6 +1,8 @@ -from pymongo import MongoClient from typing import Optional +from pymongo import MongoClient + + class Database: _instance: Optional["Database"] = None diff --git a/src/gui/reasoning_gui.py b/src/gui/reasoning_gui.py index 61fb3456..340791ee 100644 --- a/src/gui/reasoning_gui.py +++ b/src/gui/reasoning_gui.py @@ -1,12 +1,12 @@ -import customtkinter as ctk -from typing import Dict, List -import json -from datetime import datetime -import time -import threading +import os import queue import sys -import os +import threading +import time +from datetime import datetime +from typing import Dict, List + +import customtkinter as ctk from dotenv import load_dotenv # 获取当前文件的目录 @@ -25,9 +25,11 @@ else: print("未找到环境配置文件") sys.exit(1) -from pymongo import MongoClient from typing import Optional +from pymongo import MongoClient + + class Database: _instance: Optional["Database"] = None diff --git a/src/plugins/chat/Segment_builder.py b/src/plugins/chat/Segment_builder.py index 09673a04..ed75f709 100644 --- a/src/plugins/chat/Segment_builder.py +++ b/src/plugins/chat/Segment_builder.py @@ -1,6 +1,5 @@ -from typing import Dict, List, Union, Optional, Any import base64 -import os +from typing import Any, Dict, List, Union """ OneBot v11 Message Segment Builder diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py index e2dfdadb..0bffaed1 100644 --- a/src/plugins/chat/__init__.py +++ b/src/plugins/chat/__init__.py @@ -1,21 +1,23 @@ -from loguru import logger -from nonebot import on_message, on_command, require, get_driver -from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageSegment -from nonebot.typing import T_State -from ...common.database import Database -from .config import global_config -import os import asyncio +import os import random -from .relationship_manager import relationship_manager -from ..schedule.schedule_generator import bot_schedule -from .willing_manager import willing_manager -from nonebot.rule import to_me -from .bot import chat_bot -from .emoji_manager import emoji_manager -from ..moods.moods import MoodManager # 导入情绪管理器 import time + +from loguru import logger +from nonebot import get_driver, on_command, on_message, require +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageSegment +from nonebot.rule import to_me +from nonebot.typing import T_State + +from ...common.database import Database +from ..moods.moods import MoodManager # 导入情绪管理器 +from ..schedule.schedule_generator import bot_schedule from ..utils.statistic import LLMStatistics +from .bot import chat_bot +from .config import global_config +from .emoji_manager import emoji_manager +from .relationship_manager import relationship_manager +from .willing_manager import willing_manager # 创建LLM统计实例 llm_stats = LLMStatistics("llm_statistics.txt") @@ -39,12 +41,11 @@ print("\033[1;32m[初始化数据库完成]\033[0m") # 导入其他模块 +from ..memory_system.memory import hippocampus, memory_graph from .bot import ChatBot -from .emoji_manager import emoji_manager + # from .message_send_control import message_sender -from .relationship_manager import relationship_manager -from .message_sender import message_manager,message_sender -from ..memory_system.memory import memory_graph,hippocampus +from .message_sender import message_manager, message_sender # 初始化表情管理器 emoji_manager.initialize() diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 51a77ee1..a02c4a05 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -1,23 +1,27 @@ -from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message as EventMessage, Bot -from .message import Message, MessageSet, Message_Sending -from .config import BotConfig, global_config -from .storage import MessageStorage -from .llm_generator import ResponseGenerator -# from .message_stream import MessageStream, MessageStreamContainer -from .topic_identifier import topic_identifier -from random import random, choice -from .emoji_manager import emoji_manager # 导入表情包管理器 -from ..moods.moods import MoodManager # 导入情绪管理器 import time -import os -from .cq_code import CQCode # 导入CQCode模块 -from .message_sender import message_manager # 导入新的消息管理器 -from .message import Message_Thinking # 导入 Message_Thinking 类 -from .relationship_manager import relationship_manager -from .willing_manager import willing_manager # 导入意愿管理器 -from .utils import is_mentioned_bot_in_txt, calculate_typing_time -from ..memory_system.memory import memory_graph,hippocampus +from random import random + from loguru import logger +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent + +from ..memory_system.memory import hippocampus +from ..moods.moods import MoodManager # 导入情绪管理器 +from .config import global_config +from .cq_code import CQCode # 导入CQCode模块 +from .emoji_manager import emoji_manager # 导入表情包管理器 +from .llm_generator import ResponseGenerator +from .message import ( + Message, + Message_Sending, + Message_Thinking, # 导入 Message_Thinking 类 + MessageSet, +) +from .message_sender import message_manager # 导入新的消息管理器 +from .relationship_manager import relationship_manager +from .storage import MessageStorage +from .utils import calculate_typing_time, is_mentioned_bot_in_txt +from .willing_manager import willing_manager # 导入意愿管理器 + class ChatBot: def __init__(self): @@ -123,6 +127,11 @@ class ChatBot: container.messages.remove(msg) # print(f"\033[1;32m[思考消息删除]\033[0m 已找到思考消息对象,开始删除") break + + # 如果找不到思考消息,直接返回 + if not thinking_message: + print(f"\033[1;33m[警告]\033[0m 未找到对应的思考消息,可能已超时被移除") + return #记录开始思考的时间,避免从思考到回复的时间太久 thinking_start_time = thinking_message.thinking_start_time diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index dff1ce07..7890b800 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -1,16 +1,13 @@ -from dataclasses import dataclass, field -from typing import Dict, Any, Optional, Set import os -import configparser +from dataclasses import dataclass, field +from typing import Dict, Optional + import tomli -import sys from loguru import logger -from nonebot import get_driver from packaging import version from packaging.version import Version, InvalidVersion from packaging.specifiers import SpecifierSet,InvalidSpecifier - @dataclass class BotConfig: """机器人配置类""" @@ -28,6 +25,12 @@ class BotConfig: talk_allowed_groups = set() talk_frequency_down_groups = set() + thinking_timeout: int = 100 # 思考时间 + + response_willing_amplifier: float = 1.0 # 回复意愿放大系数 + response_interested_rate_amplifier: float = 1.0 # 回复兴趣度放大系数 + down_frequency_rate: float = 3.5 # 降低回复频率的群组回复意愿降低系数 + ban_user_id = set() build_memory_interval: int = 30 # 记忆构建间隔(秒) @@ -65,13 +68,20 @@ class BotConfig: mood_decay_rate: float = 0.95 # 情绪衰减率 mood_intensity_factor: float = 0.7 # 情绪强度因子 + keywords_reaction_rules = [] # 关键词回复规则 + # 默认人设 PROMPT_PERSONALITY=[ "曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧", - "是一个女大学生,你有黑色头发,你会刷小红书" + "是一个女大学生,你有黑色头发,你会刷小红书", + "是一个女大学生,你会刷b站,对ACG文化感兴趣" ] PROMPT_SCHEDULE_GEN="一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书" + PERSONALITY_1: float = 0.6 # 第一种人格概率 + PERSONALITY_2: float = 0.3 # 第二种人格概率 + PERSONALITY_3: float = 0.1 # 第三种人格概率 + @staticmethod def get_config_dir() -> str: """获取配置文件目录""" @@ -139,13 +149,18 @@ class BotConfig: config = cls() def personality(parent: dict): - personality_config = parent['personality'] - personality = personality_config.get('prompt_personality') + personality_config=parent['personality'] + personality=personality_config.get('prompt_personality') if len(personality) >= 2: logger.info(f"载入自定义人格:{personality}") config.PROMPT_PERSONALITY=personality_config.get('prompt_personality',config.PROMPT_PERSONALITY) logger.info(f"载入自定义日程prompt:{personality_config.get('prompt_schedule',config.PROMPT_SCHEDULE_GEN)}") config.PROMPT_SCHEDULE_GEN=personality_config.get('prompt_schedule',config.PROMPT_SCHEDULE_GEN) + + if config.INNER_VERSION in SpecifierSet(">=0.0.2"): + config.PERSONALITY_1=personality_config.get('personality_1_probability',config.PERSONALITY_1) + config.PERSONALITY_2=personality_config.get('personality_2_probability',config.PERSONALITY_2) + config.PERSONALITY_3=personality_config.get('personality_3_probability',config.PERSONALITY_3) def emoji(parent: dict): emoji_config = parent["emoji"] @@ -246,6 +261,12 @@ class BotConfig: config.emoji_chance = msg_config.get("emoji_chance", config.emoji_chance) config.ban_words=msg_config.get("ban_words",config.ban_words) + if config.INNER_VERSION in SpecifierSet(">=0.0.2"): + config.thinking_timeout = msg_config.get("thinking_timeout", config.thinking_timeout) + config.response_willing_amplifier = msg_config.get("response_willing_amplifier", config.response_willing_amplifier) + config.response_interested_rate_amplifier = msg_config.get("response_interested_rate_amplifier", config.response_interested_rate_amplifier) + config.down_frequency_rate = msg_config.get("down_frequency_rate", config.down_frequency_rate) + def memory(parent: dict): memory_config = parent["memory"] config.build_memory_interval = memory_config.get("build_memory_interval", config.build_memory_interval) @@ -257,6 +278,11 @@ class BotConfig: config.mood_decay_rate = mood_config.get("mood_decay_rate", config.mood_decay_rate) config.mood_intensity_factor = mood_config.get("mood_intensity_factor", config.mood_intensity_factor) + def keywords_reaction(parent: dict): + keywords_reaction_config = parent["keywords_reaction"] + if keywords_reaction_config.get("enable", False): + config.keywords_reaction_rules = keywords_reaction_config.get("rules", config.keywords_reaction_rules) + def groups(parent: dict): groups_config = parent["groups"] config.talk_allowed_groups = set(groups_config.get("talk_allowed", [])) @@ -269,7 +295,7 @@ class BotConfig: config.enable_kuuki_read = others_config.get("enable_kuuki_read", config.enable_kuuki_read) # 版本表达式:>=1.0.0,<2.0.0 - # 允许字段:func: method, support: str, notice: str + # 允许字段:func: method, support: str, notice: str, necessary: bool # 如果使用 notice 字段,在该组配置加载时,会展示该字段对用户的警示 # 例如:"notice": "personality 将在 1.3.2 后被移除",那么在有效版本中的用户就会虽然可以 # 正常执行程序,但是会看到这条自定义提示 @@ -310,6 +336,11 @@ class BotConfig: "func": mood, "support": ">=0.0.0" }, + "keywords_reaction": { + "func": keywords_reaction, + "support": ">=0.0.2", + "necessary": False + }, "groups": { "func": groups, "support": ">=0.0.0" @@ -327,7 +358,11 @@ class BotConfig: if os.path.exists(config_path): with open(config_path, "rb") as f: - toml_dict = tomli.load(f) + try: + toml_dict = tomli.load(f) + except(tomli.TOMLDecodeError) as e: + logger.critical(f"配置文件bot_config.toml填写有误,请检查第{e.lineno}行第{e.colno}处:{e.msg}") + exit(1) # 获取配置文件版本 config.INNER_VERSION = cls.get_config_version(toml_dict) @@ -339,7 +374,7 @@ class BotConfig: # 检查配置文件版本是否在支持范围内 if config.INNER_VERSION in group_specifierset: - # 如果版本在支持范围内,检查是否在支持的末端 + # 如果版本在支持范围内,检查是否存在通知 if 'notice' in include_configs[key]: logger.warning(include_configs[key]["notice"]) @@ -352,7 +387,13 @@ class BotConfig: f"当前程序仅支持以下版本范围: {group_specifierset}" ) raise InvalidVersion(f"当前程序仅支持以下版本范围: {group_specifierset}") - + + # 如果 necessary 项目存在,而且显式声明是 False,进入特殊处理 + elif "necessary" in include_configs[key] and include_configs[key].get("necessary") == False: + # 通过 pass 处理的项虽然直接忽略也是可以的,但是为了不增加理解困难,依然需要在这里显式处理 + if key == "keywords_reaction": + pass + else: # 如果用户根本没有需要的配置项,提示缺少配置 logger.error(f"配置文件中缺少必需的字段: '{key}'") diff --git a/src/plugins/chat/cq_code.py b/src/plugins/chat/cq_code.py index af2651ec..4a295e3d 100644 --- a/src/plugins/chat/cq_code.py +++ b/src/plugins/chat/cq_code.py @@ -1,24 +1,23 @@ -from dataclasses import dataclass -from typing import Dict, Optional, List, Union -import html -import requests import base64 -from PIL import Image +import html import os -from random import random -from nonebot.adapters.onebot.v11 import Bot -from .config import global_config import time -import asyncio -from .utils_image import storage_image, storage_emoji -from .utils_user import get_user_nickname -from ..models.utils_model import LLM_request -from .mapper import emojimapper +from dataclasses import dataclass +from typing import Dict, Optional + +import requests + # 解析各种CQ码 # 包含CQ码类 import urllib3 -from urllib3.util import create_urllib3_context from nonebot import get_driver +from urllib3.util import create_urllib3_context + +from ..models.utils_model import LLM_request +from .config import global_config +from .mapper import emojimapper +from .utils_image import storage_emoji, storage_image +from .utils_user import get_user_nickname driver = get_driver() config = driver.config @@ -81,7 +80,7 @@ class CQCode: if user_nickname: self.translated_plain_text = f"[@{user_nickname}]" else: - self.translated_plain_text = f"@某人" + self.translated_plain_text = "@某人" elif self.type == 'reply': self.translated_plain_text = await self.translate_reply() elif self.type == 'face': @@ -308,7 +307,7 @@ class CQCode: return f"[回复 {self.reply_message.sender.nickname} 的消息: {message_obj.processed_plain_text}]" else: - print(f"\033[1;31m[错误]\033[0m 回复消息的sender.user_id为空") + print("\033[1;31m[错误]\033[0m 回复消息的sender.user_id为空") return '[回复某人消息]' @staticmethod diff --git a/src/plugins/chat/emoji_manager.py b/src/plugins/chat/emoji_manager.py index 432d1175..4f263773 100644 --- a/src/plugins/chat/emoji_manager.py +++ b/src/plugins/chat/emoji_manager.py @@ -1,27 +1,18 @@ -from typing import List, Dict, Optional -import random -from ...common.database import Database -import os -import json -from dataclasses import dataclass -import jieba.analyse as jieba_analyse -import aiohttp -import hashlib -from datetime import datetime -import base64 -import shutil import asyncio +import os +import random import time -from PIL import Image -import io -from loguru import logger import traceback +from typing import Optional +from loguru import logger from nonebot import get_driver + +from ...common.database import Database from ..chat.config import global_config -from ..models.utils_model import LLM_request -from ..chat.utils_image import image_path_to_base64 from ..chat.utils import get_embedding +from ..chat.utils_image import image_path_to_base64 +from ..models.utils_model import LLM_request driver = get_driver() config = driver.config @@ -275,7 +266,7 @@ class EmojiManager: async def _periodic_scan(self, interval_MINS: int = 10): """定期扫描新表情包""" while True: - print(f"\033[1;36m[表情包]\033[0m 开始扫描新表情包...") + print("\033[1;36m[表情包]\033[0m 开始扫描新表情包...") await self.scan_new_emojis() await asyncio.sleep(interval_MINS * 60) # 每600秒扫描一次 diff --git a/src/plugins/chat/llm_generator.py b/src/plugins/chat/llm_generator.py index 4954c849..1ac421e6 100644 --- a/src/plugins/chat/llm_generator.py +++ b/src/plugins/chat/llm_generator.py @@ -1,19 +1,16 @@ -from typing import Dict, Any, List, Optional, Union, Tuple -from openai import OpenAI -import asyncio -from functools import partial -from .message import Message -from .config import global_config -from ...common.database import Database import random import time -import numpy as np -from .relationship_manager import relationship_manager -from .prompt_builder import prompt_builder -from .config import global_config -from .utils import process_llm_response +from typing import List, Optional, Tuple, Union + from nonebot import get_driver + +from ...common.database import Database from ..models.utils_model import LLM_request +from .config import global_config +from .message import Message +from .prompt_builder import prompt_builder +from .relationship_manager import relationship_manager +from .utils import process_llm_response driver = get_driver() config = driver.config diff --git a/src/plugins/chat/message.py b/src/plugins/chat/message.py index a39cf293..f1fc5569 100644 --- a/src/plugins/chat/message.py +++ b/src/plugins/chat/message.py @@ -1,16 +1,12 @@ -from dataclasses import dataclass -from typing import List, Optional, Dict, Tuple, ForwardRef import time -import jieba.analyse as jieba_analyse -import os -from datetime import datetime -from ...common.database import Database -from PIL import Image -from .config import global_config +from dataclasses import dataclass +from typing import Dict, ForwardRef, List, Optional + import urllib3 -from .utils_user import get_user_nickname,get_user_cardname,get_groupname + +from .cq_code import CQCode, cq_code_tool from .utils_cq import parse_cq_code -from .cq_code import cq_code_tool,CQCode +from .utils_user import get_groupname, get_user_cardname, get_user_nickname Message = ForwardRef('Message') # 添加这行 # 禁用SSL警告 diff --git a/src/plugins/chat/message_sender.py b/src/plugins/chat/message_sender.py index 3e30b3cb..050c59d7 100644 --- a/src/plugins/chat/message_sender.py +++ b/src/plugins/chat/message_sender.py @@ -1,14 +1,15 @@ -from typing import Union, List, Optional, Dict -from collections import deque -from .message import Message, Message_Thinking, MessageSet, Message_Sending -import time import asyncio +import time +from typing import Dict, List, Optional, Union + from nonebot.adapters.onebot.v11 import Bot -from .config import global_config -from .storage import MessageStorage + from .cq_code import cq_code_tool -import random +from .message import Message, Message_Sending, Message_Thinking, MessageSet +from .storage import MessageStorage from .utils import calculate_typing_time +from .config import global_config + class Message_Sender: """发送器""" @@ -161,8 +162,12 @@ class MessageManager: #优先等待这条消息 message_earliest.update_thinking_time() thinking_time = message_earliest.thinking_time - if thinking_time % 10 == 0: - print(f"\033[1;34m[调试]\033[0m 消息正在思考中,已思考{int(thinking_time)}秒") + print(f"\033[1;34m[调试]\033[0m 消息正在思考中,已思考{int(thinking_time)}秒\033[K\r", end='', flush=True) + + # 检查是否超时 + if thinking_time > global_config.thinking_timeout: + print(f"\033[1;33m[警告]\033[0m 消息思考超时({thinking_time}秒),移除该消息") + container.remove_message(message_earliest) else:# 如果不是message_thinking就只能是message_sending print(f"\033[1;34m[调试]\033[0m 消息'{message_earliest.processed_plain_text}'正在发送中") #直接发,等什么呢 @@ -200,7 +205,7 @@ class MessageManager: # 安全地移除消息 if not container.remove_message(msg): - print(f"\033[1;33m[警告]\033[0m 尝试删除不存在的消息") + print("\033[1;33m[警告]\033[0m 尝试删除不存在的消息") except Exception as e: print(f"\033[1;31m[错误]\033[0m 处理超时消息时发生错误: {e}") continue diff --git a/src/plugins/chat/prompt_builder.py b/src/plugins/chat/prompt_builder.py index 7c398583..fdb887af 100644 --- a/src/plugins/chat/prompt_builder.py +++ b/src/plugins/chat/prompt_builder.py @@ -1,17 +1,14 @@ -import time import random -from ..schedule.schedule_generator import bot_schedule -import os -from .utils import get_embedding, combine_messages, get_recent_group_detailed_plain_text +import time +from typing import Optional + from ...common.database import Database -from .config import global_config -from .topic_identifier import topic_identifier -from ..memory_system.memory import memory_graph,hippocampus -from random import choice -import numpy as np -import jieba -from collections import Counter +from ..memory_system.memory import hippocampus, memory_graph from ..moods.moods import MoodManager +from ..schedule.schedule_generator import bot_schedule +from .config import global_config +from .utils import get_embedding, get_recent_group_detailed_plain_text + class PromptBuilder: def __init__(self): @@ -25,7 +22,7 @@ class PromptBuilder: message_txt: str, sender_name: str = "某人", relationship_value: float = 0.0, - group_id: int = None) -> str: + group_id: Optional[int] = None) -> tuple[str, str]: """构建prompt Args: @@ -101,7 +98,7 @@ class PromptBuilder: for memory in relevant_memories: memory_items.append(f"关于「{memory['topic']}」的记忆:{memory['content']}") - memory_prompt = f"看到这些聊天,你想起来:\n" + "\n".join(memory_items) + "\n" + memory_prompt = "看到这些聊天,你想起来:\n" + "\n".join(memory_items) + "\n" # 打印调试信息 print("\n\033[1;32m[记忆检索]\033[0m 找到以下相关记忆:") @@ -117,25 +114,41 @@ class PromptBuilder: activate_prompt = '' activate_prompt = f"以上是群里正在进行的聊天,{memory_prompt} 现在昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和他{relation_prompt},{mood_prompt},你想要{relation_prompt_2}。" - #检测机器人相关词汇 - bot_keywords = ['人机', 'bot', '机器', '入机', 'robot', '机器人'] - is_bot = any(keyword in message_txt.lower() for keyword in bot_keywords) - if is_bot: - is_bot_prompt = '有人说你是人机或者机器人,否定这一事实,攻击或反问他。注意,只有认为你是机器人才回复,否则不要否认' - else: - is_bot_prompt = '' + #检测机器人相关词汇,改为关键词检测与反应功能了,提取到全局配置中 + # bot_keywords = ['人机', 'bot', '机器', '入机', 'robot', '机器人'] + # is_bot = any(keyword in message_txt.lower() for keyword in bot_keywords) + # if is_bot: + # is_bot_prompt = '有人说你是人机或者机器人,否定这一事实,攻击或反问他。注意,只有认为你是机器人才回复,否则不要否认' + # else: + # is_bot_prompt = '' + + # 关键词检测与反应 + keywords_reaction_prompt = '' + for rule in global_config.keywords_reaction_rules: + if rule.get("enable", False): + if any(keyword in message_txt.lower() for keyword in rule.get("keywords", [])): + print(f"检测到以下关键词之一:{rule.get('keywords', [])},触发反应:{rule.get('reaction', '')}") + keywords_reaction_prompt += rule.get("reaction", "") + ',' + #人格选择 personality=global_config.PROMPT_PERSONALITY + probability_1 = global_config.PERSONALITY_1 + probability_2 = global_config.PERSONALITY_2 + probability_3 = global_config.PERSONALITY_3 prompt_personality = '' personality_choice = random.random() - if personality_choice < 4/6: # 第一种人格 + if personality_choice < probability_1: # 第一种人格 prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[0]}, 你正在浏览qq群,{promt_info_prompt}, - 现在请你给出日常且口语化的回复,平淡一些,尽量简短一些。{is_bot_prompt} + 现在请你给出日常且口语化的回复,平淡一些,尽量简短一些。{keywords_reaction_prompt} 请注意把握群里的聊天内容,不要刻意突出自身学科背景,不要回复的太有条理,可以有个性。''' - elif personality_choice < 1: # 第二种人格 + elif personality_choice < probability_1 + probability_2: # 第二种人格 prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[1]}, 你正在浏览qq群,{promt_info_prompt}, - 现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{is_bot_prompt} + 现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{keywords_reaction_prompt} + 请你表达自己的见解和观点。可以有个性。''' + else: # 第三种人格 + prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[2]}, 你正在浏览qq群,{promt_info_prompt}, + 现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{keywords_reaction_prompt} 请你表达自己的见解和观点。可以有个性。''' #中文高手(新加的好玩功能) @@ -163,10 +176,12 @@ class PromptBuilder: activate_prompt_check=f"以上是群里正在进行的聊天,昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和他{relation_prompt},你想要{relation_prompt_2},但是这不一定是合适的时机,请你决定是否要回应这条消息。" prompt_personality_check = '' extra_check_info=f"请注意把握群里的聊天内容的基础上,综合群内的氛围,例如,和{global_config.BOT_NICKNAME}相关的话题要积极回复,如果是at自己的消息一定要回复,如果自己正在和别人聊天一定要回复,其他话题如果合适搭话也可以回复,如果认为应该回复请输出yes,否则输出no,请注意是决定是否需要回复,而不是编写回复内容,除了yes和no不要输出任何回复内容。" - if personality_choice < 4/6: # 第一种人格 + if personality_choice < probability_1: # 第一种人格 prompt_personality_check = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[0]}, 你正在浏览qq群,{promt_info_prompt} {activate_prompt_check} {extra_check_info}''' - elif personality_choice < 1: # 第二种人格 + elif personality_choice < probability_1 + probability_2: # 第二种人格 prompt_personality_check = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[1]}, 你正在浏览qq群,{promt_info_prompt} {activate_prompt_check} {extra_check_info}''' + else: # 第三种人格 + prompt_personality_check = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[2]}, 你正在浏览qq群,{promt_info_prompt} {activate_prompt_check} {extra_check_info}''' prompt_check_if_response=f"{prompt_info}\n{prompt_date}\n{chat_talking_prompt}\n{prompt_personality_check}" @@ -194,14 +209,16 @@ class PromptBuilder: #激活prompt构建 activate_prompt = '' - activate_prompt = f"以上是群里正在进行的聊天。" + activate_prompt = "以上是群里正在进行的聊天。" personality=global_config.PROMPT_PERSONALITY prompt_personality = '' personality_choice = random.random() - if personality_choice < 4/6: # 第一种人格 + if personality_choice < probability_1: # 第一种人格 prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[0]}''' - elif personality_choice < 1: # 第二种人格 + elif personality_choice < probability_1 + probability_2: # 第二种人格 prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[1]}''' + else: # 第三种人格 + prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[2]}''' topics_str=','.join(f"\"{topics}\"") prompt_for_select=f"你现在想在群里发言,回忆了一下,想到几个话题,分别是{topics_str},综合当前状态以及群内气氛,请你在其中选择一个合适的话题,注意只需要输出话题,除了话题什么也不要输出(双引号也不要输出)" diff --git a/src/plugins/chat/relationship_manager.py b/src/plugins/chat/relationship_manager.py index 29a4334e..4ed7a2f1 100644 --- a/src/plugins/chat/relationship_manager.py +++ b/src/plugins/chat/relationship_manager.py @@ -1,8 +1,8 @@ -import time -from ...common.database import Database -from nonebot.adapters.onebot.v11 import Bot -from typing import Optional, Tuple import asyncio +from typing import Optional + +from ...common.database import Database + class Impression: traits: str = None @@ -123,7 +123,7 @@ class RelationshipManager: print(f"\033[1;32m[关系管理]\033[0m 已加载 {len(self.relationships)} 条关系记录") while True: - print(f"\033[1;32m[关系管理]\033[0m 正在自动保存关系") + print("\033[1;32m[关系管理]\033[0m 正在自动保存关系") await asyncio.sleep(300) # 等待300秒(5分钟) await self._save_all_relationships() diff --git a/src/plugins/chat/storage.py b/src/plugins/chat/storage.py index 08b52b7c..6a87480b 100644 --- a/src/plugins/chat/storage.py +++ b/src/plugins/chat/storage.py @@ -1,10 +1,8 @@ -from typing import Dict, List, Any, Optional -import time -import threading -from collections import defaultdict -import asyncio -from .message import Message +from typing import Optional + from ...common.database import Database +from .message import Message + class MessageStorage: def __init__(self): diff --git a/src/plugins/chat/topic_identifier.py b/src/plugins/chat/topic_identifier.py index 6579d15a..3296d089 100644 --- a/src/plugins/chat/topic_identifier.py +++ b/src/plugins/chat/topic_identifier.py @@ -1,10 +1,9 @@ -from typing import Optional, Dict, List -from openai import OpenAI -from .message import Message -import jieba +from typing import List, Optional + from nonebot import get_driver -from .config import global_config + from ..models.utils_model import LLM_request +from .config import global_config driver = get_driver() config = driver.config @@ -26,7 +25,7 @@ class TopicIdentifier: topic, _ = await self.llm_topic_judge.generate_response(prompt) if not topic: - print(f"\033[1;31m[错误]\033[0m LLM API 返回为空") + print("\033[1;31m[错误]\033[0m LLM API 返回为空") return None # 直接在这里处理主题解析 diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index db5a3538..d079a2eb 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -1,19 +1,18 @@ -import time -import random -from typing import List -from .message import Message -import requests -import numpy as np -from .config import global_config -import re -from typing import Dict -from collections import Counter import math -from nonebot import get_driver -from ..models.utils_model import LLM_request -import aiohttp +import random +import time +from collections import Counter +from typing import Dict, List + import jieba +import numpy as np +from nonebot import get_driver + +from ..models.utils_model import LLM_request from ..utils.typo_generator import ChineseTypoGenerator +from .config import global_config +from .message import Message +from ..moods.moods import MoodManager driver = get_driver() config = driver.config @@ -328,7 +327,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) > 300: + if len(text) > 200: print(f"回复过长 ({len(text)} 字符),返回默认回复") return ['懒得说'] # 处理长消息 @@ -338,30 +337,55 @@ def process_llm_response(text: str) -> List[str]: tone_error_rate=0.2, word_replace_rate=0.02 ) - typoed_text = typo_generator.create_typo_sentence(text)[0] - sentences = split_into_sentences_w_remove_punctuation(typoed_text) + split_sentences = split_into_sentences_w_remove_punctuation(text) + sentences = [] + for sentence in split_sentences: + typoed_text, typo_corrections = typo_generator.create_typo_sentence(sentence) + sentences.append(typoed_text) + if typo_corrections: + sentences.append(typo_corrections) # 检查分割后的消息数量是否过多(超过3条) - if len(sentences) > 4: + + if len(sentences) > 5: print(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复") return [f'{global_config.BOT_NICKNAME}不知道哦'] return sentences -def calculate_typing_time(input_string: str, chinese_time: float = 0.2, english_time: float = 0.1) -> float: +def calculate_typing_time(input_string: str, chinese_time: float = 0.4, english_time: float = 0.2) -> float: """ 计算输入字符串所需的时间,中文和英文字符有不同的输入时间 input_string (str): 输入的字符串 - chinese_time (float): 中文字符的输入时间,默认为0.3秒 - english_time (float): 英文字符的输入时间,默认为0.15秒 + chinese_time (float): 中文字符的输入时间,默认为0.2秒 + english_time (float): 英文字符的输入时间,默认为0.1秒 + + 特殊情况: + - 如果只有一个中文字符,将使用3倍的中文输入时间 + - 在所有输入结束后,额外加上回车时间0.3秒 """ + mood_manager = MoodManager.get_instance() + # 将0-1的唤醒度映射到-1到1 + mood_arousal = mood_manager.current_mood.arousal + # 映射到0.5到2倍的速度系数 + typing_speed_multiplier = 1.5 ** mood_arousal # 唤醒度为1时速度翻倍,为-1时速度减半 + chinese_time *= 1/typing_speed_multiplier + english_time *= 1/typing_speed_multiplier + # 计算中文字符数 + chinese_chars = sum(1 for char in input_string if '\u4e00' <= char <= '\u9fff') + + # 如果只有一个中文字符,使用3倍时间 + if chinese_chars == 1 and len(input_string.strip()) == 1: + return chinese_time * 3 + 0.3 # 加上回车时间 + + # 正常计算所有字符的输入时间 total_time = 0.0 for char in input_string: if '\u4e00' <= char <= '\u9fff': # 判断是否为中文字符 total_time += chinese_time else: # 其他字符(如英文) total_time += english_time - return total_time + return total_time + 0.3 # 加上回车时间 def cosine_similarity(v1, v2): diff --git a/src/plugins/chat/utils_image.py b/src/plugins/chat/utils_image.py index eff78886..8a8b3ce5 100644 --- a/src/plugins/chat/utils_image.py +++ b/src/plugins/chat/utils_image.py @@ -1,14 +1,15 @@ +import base64 import io -from PIL import Image -import hashlib -import time import os +import time +import zlib # 用于 CRC32 + +from loguru import logger +from nonebot import get_driver +from PIL import Image + from ...common.database import Database from ..chat.config import global_config -import zlib # 用于 CRC32 -import base64 -from nonebot import get_driver -from loguru import logger driver = get_driver() config = driver.config @@ -119,7 +120,7 @@ def storage_compress_image(base64_data: str, max_size: int = 200) -> str: # 保存记录 collection.insert_one(image_record) - print(f"\033[1;32m[成功]\033[0m 保存图片记录到数据库") + print("\033[1;32m[成功]\033[0m 保存图片记录到数据库") except Exception as db_error: print(f"\033[1;31m[错误]\033[0m 数据库操作失败: {str(db_error)}") diff --git a/src/plugins/chat/utils_user.py b/src/plugins/chat/utils_user.py index bb8c3094..489eb7a1 100644 --- a/src/plugins/chat/utils_user.py +++ b/src/plugins/chat/utils_user.py @@ -1,5 +1,6 @@ -from .relationship_manager import relationship_manager from .config import global_config +from .relationship_manager import relationship_manager + def get_user_nickname(user_id: int) -> str: if int(user_id) == int(global_config.BOT_QQ): diff --git a/src/plugins/chat/willing_manager.py b/src/plugins/chat/willing_manager.py index 16a0570e..001b6620 100644 --- a/src/plugins/chat/willing_manager.py +++ b/src/plugins/chat/willing_manager.py @@ -1,4 +1,6 @@ import asyncio +from .config import global_config + class WillingManager: def __init__(self): @@ -37,11 +39,14 @@ class WillingManager: current_willing *= 0.1 print(f"表情包, 当前意愿: {current_willing}") + print(f"放大系数_interested_rate: {global_config.response_interested_rate_amplifier}") + interested_rate *= global_config.response_interested_rate_amplifier #放大回复兴趣度 if interested_rate > 0.4: - print(f"兴趣度: {interested_rate}, 当前意愿: {current_willing}") - current_willing += interested_rate-0.1 + # print(f"兴趣度: {interested_rate}, 当前意愿: {current_willing}") + current_willing += interested_rate-0.4 - self.group_reply_willing[group_id] = min(current_willing, 3.0) + current_willing *= global_config.response_willing_amplifier #放大回复意愿 + # print(f"放大系数_willing: {global_config.response_willing_amplifier}, 当前意愿: {current_willing}") reply_probability = max((current_willing - 0.45) * 2, 0) if group_id not in config.talk_allowed_groups: @@ -49,11 +54,14 @@ class WillingManager: reply_probability = 0 if group_id in config.talk_frequency_down_groups: - reply_probability = reply_probability / 3.5 + reply_probability = reply_probability / global_config.down_frequency_rate reply_probability = min(reply_probability, 1) if reply_probability < 0: reply_probability = 0 + + + self.group_reply_willing[group_id] = min(current_willing, 3.0) return reply_probability def change_reply_willing_sent(self, group_id: int): diff --git a/src/plugins/knowledege/knowledge_library.py b/src/plugins/knowledege/knowledge_library.py index d7071985..d2408e24 100644 --- a/src/plugins/knowledege/knowledge_library.py +++ b/src/plugins/knowledege/knowledge_library.py @@ -1,8 +1,8 @@ import os import sys -import numpy as np -import requests import time + +import requests from dotenv import load_dotenv # 添加项目根目录到 Python 路径 diff --git a/src/plugins/memory_system/draw_memory.py b/src/plugins/memory_system/draw_memory.py index fad3f5f3..006991bc 100644 --- a/src/plugins/memory_system/draw_memory.py +++ b/src/plugins/memory_system/draw_memory.py @@ -1,19 +1,12 @@ # -*- coding: utf-8 -*- import os import sys -import jieba -import networkx as nx -import matplotlib.pyplot as plt -import math -from collections import Counter -import datetime -import random import time + +import jieba +import matplotlib.pyplot as plt +import networkx as nx from dotenv import load_dotenv -import sys -import asyncio -import aiohttp -from typing import Tuple sys.path.append("C:/GitHub/MaiMBot") # 添加项目根目录到 Python 路径 from src.common.database import Database # 使用正确的导入语法 diff --git a/src/plugins/memory_system/memory.py b/src/plugins/memory_system/memory.py index 44f5eb71..f88888aa 100644 --- a/src/plugins/memory_system/memory.py +++ b/src/plugins/memory_system/memory.py @@ -1,20 +1,21 @@ # -*- coding: utf-8 -*- -import os -import jieba -import networkx as nx -import matplotlib.pyplot as plt -from collections import Counter import datetime +import math import random import time + +import jieba +import networkx as nx + +from ...common.database import Database # 使用正确的导入语法 from ..chat.config import global_config -from ...common.database import Database # 使用正确的导入语法 +from ..chat.utils import ( + calculate_information_content, + cosine_similarity, + get_cloest_chat_from_db, + text_to_vector, +) from ..models.utils_model import LLM_request -import math -from ..chat.utils import calculate_information_content, get_cloest_chat_from_db ,text_to_vector,cosine_similarity - - - class Memory_graph: @@ -530,7 +531,8 @@ class Hippocampus: # 计算每个识别出的主题与记忆主题的相似度 for topic in topics: if debug_info: - print(f"\033[1;32m[{debug_info}]\033[0m 正在思考有没有见过: {topic}") + # print(f"\033[1;32m[{debug_info}]\033[0m 正在思考有没有见过: {topic}") + pass topic_vector = text_to_vector(topic) has_similar_topic = False @@ -548,11 +550,13 @@ class Hippocampus: if similarity >= similarity_threshold: has_similar_topic = True if debug_info: - print(f"\033[1;32m[{debug_info}]\033[0m 找到相似主题: {topic} -> {memory_topic} (相似度: {similarity:.2f})") + # print(f"\033[1;32m[{debug_info}]\033[0m 找到相似主题: {topic} -> {memory_topic} (相似度: {similarity:.2f})") + pass all_similar_topics.append((memory_topic, similarity)) if not has_similar_topic and debug_info: - print(f"\033[1;31m[{debug_info}]\033[0m 没有见过: {topic} ,呃呃") + # print(f"\033[1;31m[{debug_info}]\033[0m 没有见过: {topic} ,呃呃") + pass return all_similar_topics @@ -696,6 +700,7 @@ def segment_text(text): from nonebot import get_driver + driver = get_driver() config = driver.config diff --git a/src/plugins/memory_system/memory_manual_build.py b/src/plugins/memory_system/memory_manual_build.py index e9948565..3124bc8e 100644 --- a/src/plugins/memory_system/memory_manual_build.py +++ b/src/plugins/memory_system/memory_manual_build.py @@ -1,21 +1,22 @@ # -*- coding: utf-8 -*- -import sys -import jieba -import networkx as nx -import matplotlib.pyplot as plt -import math -from collections import Counter import datetime -import random -import time +import math import os -from dotenv import load_dotenv -import pymongo -from loguru import logger +import random +import sys +import time +from collections import Counter from pathlib import Path + +import matplotlib.pyplot as plt +import networkx as nx +import pymongo +from dotenv import load_dotenv +from loguru import logger + # from chat.config import global_config sys.path.append("C:/GitHub/MaiMBot") # 添加项目根目录到 Python 路径 -from src.common.database import Database +from src.common.database import Database from src.plugins.memory_system.offline_llm import LLMModel # 获取当前文件的目录 @@ -102,7 +103,7 @@ def get_cloest_chat_from_db(db, length: int, timestamp: str): # 检查当前记录的memorized值 current_memorized = record.get('memorized', 0) if current_memorized > 3: - print(f"消息已读取3次,跳过") + print("消息已读取3次,跳过") return '' # 更新memorized值 @@ -114,7 +115,7 @@ def get_cloest_chat_from_db(db, length: int, timestamp: str): chat_text += record["detailed_plain_text"] return chat_text - print(f"消息已读取3次,跳过") + print("消息已读取3次,跳过") return '' class Memory_graph: diff --git a/src/plugins/memory_system/offline_llm.py b/src/plugins/memory_system/offline_llm.py index 5e877dce..4a80b3ec 100644 --- a/src/plugins/memory_system/offline_llm.py +++ b/src/plugins/memory_system/offline_llm.py @@ -1,11 +1,13 @@ -import os -import requests -from typing import Tuple, Union -import time -import aiohttp import asyncio +import os +import time +from typing import Tuple, Union + +import aiohttp +import requests from loguru import logger + class LLMModel: def __init__(self, model_name="deepseek-ai/DeepSeek-V3", **kwargs): self.model_name = model_name diff --git a/src/plugins/models/utils_model.py b/src/plugins/models/utils_model.py index 0a72cfec..e890b4c8 100644 --- a/src/plugins/models/utils_model.py +++ b/src/plugins/models/utils_model.py @@ -1,16 +1,16 @@ -import aiohttp import asyncio import json -import requests -import time import re +from datetime import datetime from typing import Tuple, Union -from nonebot import get_driver + +import aiohttp from loguru import logger +from nonebot import get_driver + +from ...common.database import Database from ..chat.config import global_config from ..chat.utils_image import compress_base64_image_by_scale -from datetime import datetime -from ...common.database import Database driver = get_driver() config = driver.config @@ -182,6 +182,13 @@ class LLM_request: continue elif response.status in policy["abort_codes"]: logger.error(f"错误码: {response.status} - {error_code_mapping.get(response.status)}") + if response.status == 403 : + if global_config.llm_normal == "Pro/deepseek-ai/DeepSeek-V3": + logger.error("可能是没有给硅基流动充钱,普通模型自动退化至非Pro模型,反应速度可能会变慢") + global_config.llm_normal = "deepseek-ai/DeepSeek-V3" + if global_config.llm_reasoning == "Pro/deepseek-ai/DeepSeek-R1": + logger.error("可能是没有给硅基流动充钱,推理模型自动退化至非Pro模型,反应速度可能会变慢") + global_config.llm_reasoning = "deepseek-ai/DeepSeek-R1" raise RuntimeError(f"请求被拒绝: {error_code_mapping.get(response.status)}") response.raise_for_status() @@ -227,7 +234,7 @@ class LLM_request: await asyncio.sleep(wait_time) else: logger.critical(f"请求失败: {str(e)}") - logger.critical(f"请求头: {await self._build_headers()} 请求体: {payload}") + logger.critical(f"请求头: {await self._build_headers(no_key=True)} 请求体: {payload}") raise RuntimeError(f"API请求失败: {str(e)}") logger.error("达到最大重试次数,请求仍然失败") @@ -325,12 +332,19 @@ class LLM_request: reasoning = "" return content, reasoning - async def _build_headers(self) -> dict: + async def _build_headers(self, no_key: bool = False) -> dict: """构建请求头""" - return { - "Authorization": f"Bearer {self.api_key}", - "Content-Type": "application/json" - } + if no_key: + return { + "Authorization": f"Bearer **********", + "Content-Type": "application/json" + } + else: + return { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + # 防止小朋友们截图自己的key async def generate_response(self, prompt: str) -> Tuple[str, str]: """根据输入的提示生成模型的异步响应""" diff --git a/src/plugins/moods/moods.py b/src/plugins/moods/moods.py index eee5da54..32b900b0 100644 --- a/src/plugins/moods/moods.py +++ b/src/plugins/moods/moods.py @@ -1,10 +1,11 @@ import math -import time import threading -from typing import Dict, Tuple, Optional +import time from dataclasses import dataclass + from ..chat.config import global_config + @dataclass class MoodState: valence: float # 愉悦度 (-1 到 1) diff --git a/src/plugins/schedule/schedule_generator.py b/src/plugins/schedule/schedule_generator.py index f2b11c33..8a036152 100644 --- a/src/plugins/schedule/schedule_generator.py +++ b/src/plugins/schedule/schedule_generator.py @@ -1,12 +1,15 @@ import datetime -import os -from typing import List, Dict, Union -from ...common.database import Database # 使用正确的导入语法 -from src.plugins.chat.config import global_config -from nonebot import get_driver -from ..models.utils_model import LLM_request -from loguru import logger import json +from typing import Dict, Union + +from loguru import logger +from nonebot import get_driver + +from src.plugins.chat.config import global_config + +from ...common.database import Database # 使用正确的导入语法 +from ..models.utils_model import LLM_request + driver = get_driver() config = driver.config diff --git a/src/plugins/utils/statistic.py b/src/plugins/utils/statistic.py index 093ace53..d7248e86 100644 --- a/src/plugins/utils/statistic.py +++ b/src/plugins/utils/statistic.py @@ -1,11 +1,12 @@ -from typing import Dict, List, Any -import time import threading -import json -from datetime import datetime, timedelta +import time from collections import defaultdict +from datetime import datetime, timedelta +from typing import Any, Dict + from ...common.database import Database + class LLMStatistics: def __init__(self, output_file: str = "llm_statistics.txt"): """初始化LLM统计类 diff --git a/src/plugins/utils/typo_generator.py b/src/plugins/utils/typo_generator.py index 16834200..aa72c387 100644 --- a/src/plugins/utils/typo_generator.py +++ b/src/plugins/utils/typo_generator.py @@ -2,15 +2,17 @@ 错别字生成器 - 基于拼音和字频的中文错别字生成工具 """ -from pypinyin import pinyin, Style -from collections import defaultdict import json -import os -import jieba -from pathlib import Path -import random import math +import os +import random import time +from collections import defaultdict +from pathlib import Path + +import jieba +from pypinyin import Style, pinyin + class ChineseTypoGenerator: def __init__(self, @@ -282,10 +284,13 @@ class ChineseTypoGenerator: 返回: typo_sentence: 包含错别字的句子 - typo_info: 错别字信息列表 + correction_suggestion: 随机选择的一个纠正建议,返回正确的字/词 """ result = [] typo_info = [] + word_typos = [] # 记录词语错误对(错词,正确词) + char_typos = [] # 记录单字错误对(错字,正确字) + current_pos = 0 # 分词 words = self._segment_sentence(sentence) @@ -294,6 +299,7 @@ class ChineseTypoGenerator: # 如果是标点符号或空格,直接添加 if all(not self._is_chinese_char(c) for c in word): result.append(word) + current_pos += len(word) continue # 获取词语的拼音 @@ -314,6 +320,8 @@ class ChineseTypoGenerator: ' '.join(word_pinyin), ' '.join(self._get_word_pinyin(typo_word)), orig_freq, typo_freq)) + word_typos.append((typo_word, word)) # 记录(错词,正确词)对 + current_pos += len(typo_word) continue # 如果不进行整词替换,则进行单字替换 @@ -331,11 +339,15 @@ class ChineseTypoGenerator: result.append(typo_char) typo_py = pinyin(typo_char, style=Style.TONE3)[0][0] typo_info.append((char, typo_char, py, typo_py, orig_freq, typo_freq)) + char_typos.append((typo_char, char)) # 记录(错字,正确字)对 + current_pos += 1 continue result.append(char) + current_pos += 1 else: # 处理多字词的单字替换 word_result = [] + word_start_pos = current_pos for i, (char, py) in enumerate(zip(word, word_pinyin)): # 词中的字替换概率降低 word_error_rate = self.error_rate * (0.7 ** (len(word) - 1)) @@ -351,11 +363,24 @@ class ChineseTypoGenerator: word_result.append(typo_char) typo_py = pinyin(typo_char, style=Style.TONE3)[0][0] typo_info.append((char, typo_char, py, typo_py, orig_freq, typo_freq)) + char_typos.append((typo_char, char)) # 记录(错字,正确字)对 continue word_result.append(char) result.append(''.join(word_result)) + current_pos += len(word) - return ''.join(result), typo_info + # 优先从词语错误中选择,如果没有则从单字错误中选择 + correction_suggestion = None + # 50%概率返回纠正建议 + if random.random() < 0.5: + if word_typos: + wrong_word, correct_word = random.choice(word_typos) + correction_suggestion = correct_word + elif char_typos: + wrong_char, correct_char = random.choice(char_typos) + correction_suggestion = correct_char + + return ''.join(result), correction_suggestion def format_typo_info(self, typo_info): """ @@ -417,16 +442,16 @@ def main(): # 创建包含错别字的句子 start_time = time.time() - typo_sentence, typo_info = typo_generator.create_typo_sentence(sentence) + typo_sentence, correction_suggestion = typo_generator.create_typo_sentence(sentence) # 打印结果 print("\n原句:", sentence) print("错字版:", typo_sentence) - # 打印错别字信息 - if typo_info: - print("\n错别字信息:") - print(typo_generator.format_typo_info(typo_info)) + # 打印纠正建议 + if correction_suggestion: + print("\n随机纠正建议:") + print(f"应该改为:{correction_suggestion}") # 计算并打印总耗时 end_time = time.time() diff --git a/template/auto_format.py b/template/auto_format.py index 9bc27da2..d99e29e3 100644 --- a/template/auto_format.py +++ b/template/auto_format.py @@ -1,8 +1,10 @@ -import tomli -import tomli_w +import os import sys from pathlib import Path -import os + +import tomli +import tomli_w + def sync_configs(): # 读取两个配置文件 diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 48c11175..991fbb86 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -8,14 +8,23 @@ nickname = "麦麦" [personality] prompt_personality = [ "曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧", # 贴吧人格 - "是一个女大学生,你有黑色头发,你会刷小红书" # 小红书人格 + "是一个女大学生,你有黑色头发,你会刷小红书", # 小红书人格 + "是一个女大学生,你会刷b站,对ACG文化感兴趣" # b站人格 ] +personality_1_probability = 0.6 # 第一种人格出现概率 +personality_2_probability = 0.3 # 第二种人格出现概率 +personality_3_probability = 0.1 # 第三种人格出现概率,请确保三个概率相加等于1 prompt_schedule = "一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书" [message] min_text_length = 2 # 与麦麦聊天时麦麦只会回答文本大于等于此数的消息 max_context_size = 15 # 麦麦获得的上文数量 emoji_chance = 0.2 # 麦麦使用表情包的概率 +thinking_timeout = 120 # 麦麦思考时间 + +response_willing_amplifier = 1 # 麦麦回复意愿放大系数,一般为1 +response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 +down_frequency_rate = 3.5 # 降低回复频率的群组回复意愿降低系数 ban_words = [ # "403","张三" ] @@ -45,6 +54,19 @@ mood_update_interval = 1.0 # 情绪更新间隔 单位秒 mood_decay_rate = 0.95 # 情绪衰减率 mood_intensity_factor = 1.0 # 情绪强度因子 +[keywords_reaction] # 针对某个关键词作出反应 +enable = true # 关键词反应功能的总开关 + +[[keywords_reaction.rules]] # 如果想要新增多个关键词,直接复制本条,修改keywords和reaction即可 +enable = true # 是否启用此条(为了人类在未来AI战争能更好地识别AI(bushi),默认开启) +keywords = ["人机", "bot", "机器", "入机", "robot", "机器人"] # 会触发反应的关键词 +reaction = "有人说你是人机或者机器人,否定这一事实,攻击或反问他。注意,只有认为你是机器人才回复,否则不要否认" # 触发之后添加的提示词 + +[[keywords_reaction.rules]] # 就像这样复制 +enable = false # 仅作示例,不会触发 +keywords = ["测试关键词回复","test",""] +reaction = "回答“测试成功”" + [others] enable_advance_output = true # 是否启用高级输出 enable_kuuki_read = true # 是否启用读空气功能