diff --git a/README.md b/README.md
index 8dea5bc15..3ff2548d7 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@
-
+
👆 点击观看麦麦演示视频 👆
@@ -149,6 +149,8 @@ MaiMBot是一个开源项目,我们非常欢迎你的参与。你的贡献,
- [📦 Linux 手动部署指南 ](docs/manual_deploy_linux.md)
+- [📦 macOS 手动部署指南 ](docs/manual_deploy_macos.md)
+
如果你不知道Docker是什么,建议寻找相关教程或使用手动部署 **(现在不建议使用docker,更新慢,可能不适配)**
- [🐳 Docker部署指南](docs/docker_deploy.md)
diff --git a/bot.py b/bot.py
index 88c07939b..30714e846 100644
--- a/bot.py
+++ b/bot.py
@@ -204,8 +204,8 @@ def check_eula():
eula_confirmed = True
eula_updated = False
if eula_new_hash == os.getenv("EULA_AGREE"):
- eula_confirmed = True
- eula_updated = False
+ eula_confirmed = True
+ eula_updated = False
# 检查隐私条款确认文件是否存在
if privacy_confirm_file.exists():
@@ -214,14 +214,16 @@ def check_eula():
if privacy_new_hash == confirmed_content:
privacy_confirmed = True
privacy_updated = False
- if privacy_new_hash == os.getenv("PRIVACY_AGREE"):
- privacy_confirmed = True
- privacy_updated = False
+ if privacy_new_hash == os.getenv("PRIVACY_AGREE"):
+ privacy_confirmed = True
+ privacy_updated = False
# 如果EULA或隐私条款有更新,提示用户重新确认
if eula_updated or privacy_updated:
print("EULA或隐私条款内容已更新,请在阅读后重新确认,继续运行视为同意更新后的以上两款协议")
- print(f'输入"同意"或"confirmed"或设置环境变量"EULA_AGREE={eula_new_hash}"和"PRIVACY_AGREE={privacy_new_hash}"继续运行')
+ print(
+ f'输入"同意"或"confirmed"或设置环境变量"EULA_AGREE={eula_new_hash}"和"PRIVACY_AGREE={privacy_new_hash}"继续运行'
+ )
while True:
user_input = input().strip().lower()
if user_input in ["同意", "confirmed"]:
diff --git a/docs/fast_q_a.md b/docs/fast_q_a.md
index 1f015565d..92800bad2 100644
--- a/docs/fast_q_a.md
+++ b/docs/fast_q_a.md
@@ -10,7 +10,7 @@
- 为什么显示:"缺失必要的API KEY" ❓
-
+
>你需要在 [Silicon Flow Api](https://cloud.siliconflow.cn/account/ak) 网站上注册一个账号,然后点击这个链接打开API KEY获取页面。
>
@@ -41,19 +41,19 @@
>打开你的MongoDB Compass软件,你会在左上角看到这样的一个界面:
>
->
+>
>
>
>
>点击 "CONNECT" 之后,点击展开 MegBot 标签栏
>
->
+>
>
>
>
>点进 "emoji" 再点击 "DELETE" 删掉所有条目,如图所示
>
->
+>
>
>
>
diff --git a/docs/linux_deploy_guide_for_beginners.md b/docs/linux_deploy_guide_for_beginners.md
index 04601923f..1f1b0899f 100644
--- a/docs/linux_deploy_guide_for_beginners.md
+++ b/docs/linux_deploy_guide_for_beginners.md
@@ -1,48 +1,51 @@
# 面向纯新手的Linux服务器麦麦部署指南
-## 你得先有一个服务器
-为了能使麦麦在你的电脑关机之后还能运行,你需要一台不间断开机的主机,也就是我们常说的服务器。
+## 事前准备
+为了能使麦麦不间断的运行,你需要一台一直开着的主机。
+### 如果你想购买服务器
华为云、阿里云、腾讯云等等都是在国内可以选择的选择。
-你可以去租一台最低配置的就足敷需要了,按月租大概十几块钱就能租到了。
+租一台最低配置的就足敷需要了,按月租大概十几块钱就能租到了。
-我们假设你已经租好了一台Linux架构的云服务器。我用的是阿里云ubuntu24.04,其他的原理相似。
+### 如果你不想购买服务器
+你可以准备一台可以一直开着的电脑/主机,只需要保证能够正常访问互联网即可
+
+我们假设你已经有了一台Linux架构的服务器。举例使用的是Ubuntu24.04,其他的原理相似。
## 0.我们就从零开始吧
### 网络问题
-为访问github相关界面,推荐去下一款加速器,新手可以试试watttoolkit。
+为访问Github相关界面,推荐去下一款加速器,新手可以试试[Watt Toolkit](https://gitee.com/rmbgame/SteamTools/releases/latest)。
### 安装包下载
#### MongoDB
+进入[MongoDB下载页](https://www.mongodb.com/try/download/community-kubernetes-operator),并选择版本
-对于ubuntu24.04 x86来说是这个:
+以Ubuntu24.04 x86为例,保持如图所示选项,点击`Download`即可,如果是其他系统,请在`Platform`中自行选择:
-https://repo.mongodb.org/apt/ubuntu/dists/noble/mongodb-org/8.0/multiverse/binary-amd64/mongodb-org-server_8.0.5_amd64.deb
+
-如果不是就在这里自行选择对应版本
-https://www.mongodb.com/try/download/community-kubernetes-operator
+不想使用上述方式?你也可以参考[官方文档](https://www.mongodb.com/zh-cn/docs/manual/administration/install-on-linux/#std-label-install-mdb-community-edition-linux)进行安装,进入后选择自己的系统版本即可
-#### Napcat
-
-在这里选择对应版本。
-
-https://github.com/NapNeko/NapCatQQ/releases/tag/v4.6.7
-
-对于ubuntu24.04 x86来说是这个:
-
-https://dldir1.qq.com/qqfile/qq/QQNT/ee4bd910/linuxqq_3.2.16-32793_amd64.deb
+#### QQ(可选)/Napcat
+*如果你使用Napcat的脚本安装,可以忽略此步*
+访问https://github.com/NapNeko/NapCatQQ/releases/latest
+在图中所示区域可以找到QQ的下载链接,选择对应版本下载即可
+从这里下载,可以保证你下载到的QQ版本兼容最新版Napcat
+
+如果你不想使用Napcat的脚本安装,还需参考[Napcat-Linux手动安装](https://www.napcat.wiki/guide/boot/Shell-Linux-SemiAuto)
#### 麦麦
-https://github.com/SengokuCola/MaiMBot/archive/refs/tags/0.5.8-alpha.zip
-
-下载这个官方压缩包。
+先打开https://github.com/MaiM-with-u/MaiBot/releases
+往下滑找到这个
+
+下载箭头所指这个压缩包。
### 路径
@@ -53,10 +56,10 @@ https://github.com/SengokuCola/MaiMBot/archive/refs/tags/0.5.8-alpha.zip
```
moi
└─ mai
- ├─ linuxqq_3.2.16-32793_amd64.deb
- ├─ mongodb-org-server_8.0.5_amd64.deb
+ ├─ linuxqq_3.2.16-32793_amd64.deb # linuxqq安装包
+ ├─ mongodb-org-server_8.0.5_amd64.deb # MongoDB的安装包
└─ bot
- └─ MaiMBot-0.5.8-alpha.zip
+ └─ MaiMBot-0.5.8-alpha.zip # 麦麦的压缩包
```
### 网络
@@ -69,7 +72,7 @@ moi
## 2. Python的安装
-- 导入 Python 的稳定版 PPA:
+- 导入 Python 的稳定版 PPA(Ubuntu需执行此步,Debian可忽略):
```bash
sudo add-apt-repository ppa:deadsnakes/ppa
@@ -92,6 +95,11 @@ sudo apt install python3.12
```bash
python3.12 --version
```
+- (可选)更新替代方案,设置 python3.12 为默认的 python3 版本:
+```bash
+sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
+sudo update-alternatives --config python3
+```
- 在「终端」中,执行以下命令安装 pip:
@@ -141,23 +149,17 @@ systemctl status mongod #通过这条指令检查运行状态
sudo systemctl enable mongod
```
-## 5.napcat的安装
+## 5.Napcat的安装
``` bash
+# 该脚本适用于支持Ubuntu 20+/Debian 10+/Centos9
curl -o napcat.sh https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh && sudo bash napcat.sh
```
-
-上面的不行试试下面的
-
-``` bash
-dpkg -i linuxqq_3.2.16-32793_amd64.deb
-apt-get install -f
-dpkg -i linuxqq_3.2.16-32793_amd64.deb
-```
+执行后,脚本会自动帮你部署好QQ及Napcat
成功的标志是输入``` napcat ```出来炫酷的彩虹色界面
-## 6.napcat的运行
+## 6.Napcat的运行
此时你就可以根据提示在```napcat```里面登录你的QQ号了。
@@ -170,6 +172,13 @@ napcat status #检查运行状态
```http://<你服务器的公网IP>:6099/webui?token=napcat```
+如果你部署在自己的电脑上:
+```http://127.0.0.1:6099/webui?token=napcat```
+
+> [!WARNING]
+> 如果你的麦麦部署在公网,请**务必**修改Napcat的默认密码
+
+
第一次是这个,后续改了密码之后token就会对应修改。你也可以使用```napcat log <你的QQ号>```来查看webui地址。把里面的```127.0.0.1```改成<你服务器的公网IP>即可。
登录上之后在网络配置界面添加websocket客户端,名称随便输一个,url改成`ws://127.0.0.1:8080/onebot/v11/ws`保存之后点启用,就大功告成了。
@@ -178,7 +187,7 @@ napcat status #检查运行状态
### step 1 安装解压软件
-```
+```bash
sudo apt-get install unzip
```
@@ -229,138 +238,11 @@ bot
你可以注册一个硅基流动的账号,通过邀请码注册有14块钱的免费额度:https://cloud.siliconflow.cn/i/7Yld7cfg。
-#### 在.env.prod中定义API凭证:
+#### 修改配置文件
+请参考
+- [🎀 新手配置指南](./installation_cute.md) - 通俗易懂的配置教程,适合初次使用的猫娘
+- [⚙️ 标准配置指南](./installation_standard.md) - 简明专业的配置说明,适合有经验的用户
-```
-# API凭证配置
-SILICONFLOW_KEY=your_key # 硅基流动API密钥
-SILICONFLOW_BASE_URL=https://api.siliconflow.cn/v1/ # 硅基流动API地址
-
-DEEP_SEEK_KEY=your_key # DeepSeek API密钥
-DEEP_SEEK_BASE_URL=https://api.deepseek.com/v1 # DeepSeek API地址
-
-CHAT_ANY_WHERE_KEY=your_key # ChatAnyWhere API密钥
-CHAT_ANY_WHERE_BASE_URL=https://api.chatanywhere.tech/v1 # ChatAnyWhere API地址
-```
-
-#### 在bot_config.toml中引用API凭证:
-
-```
-[model.llm_reasoning]
-name = "Pro/deepseek-ai/DeepSeek-R1"
-base_url = "SILICONFLOW_BASE_URL" # 引用.env.prod中定义的地址
-key = "SILICONFLOW_KEY" # 引用.env.prod中定义的密钥
-```
-
-如需切换到其他API服务,只需修改引用:
-
-```
-[model.llm_reasoning]
-name = "Pro/deepseek-ai/DeepSeek-R1"
-base_url = "DEEP_SEEK_BASE_URL" # 切换为DeepSeek服务
-key = "DEEP_SEEK_KEY" # 使用DeepSeek密钥
-```
-
-#### 配置文件详解
-
-##### 环境配置文件 (.env.prod)
-
-```
-# API配置
-SILICONFLOW_KEY=your_key
-SILICONFLOW_BASE_URL=https://api.siliconflow.cn/v1/
-DEEP_SEEK_KEY=your_key
-DEEP_SEEK_BASE_URL=https://api.deepseek.com/v1
-CHAT_ANY_WHERE_KEY=your_key
-CHAT_ANY_WHERE_BASE_URL=https://api.chatanywhere.tech/v1
-
-# 服务配置
-HOST=127.0.0.1 # 如果使用Docker部署,需要改成0.0.0.0,否则QQ消息无法传入
-PORT=8080
-
-# 数据库配置
-MONGODB_HOST=127.0.0.1 # 如果使用Docker部署,需要改成数据库容器的名字,默认是mongodb
-MONGODB_PORT=27017
-DATABASE_NAME=MegBot
-MONGODB_USERNAME = "" # 数据库用户名
-MONGODB_PASSWORD = "" # 数据库密码
-MONGODB_AUTH_SOURCE = "" # 认证数据库
-
-# 插件配置
-PLUGINS=["src2.plugins.chat"]
-```
-
-##### 机器人配置文件 (bot_config.toml)
-
-```
-[bot]
-qq = "机器人QQ号" # 必填
-nickname = "麦麦" # 机器人昵称(你希望机器人怎么称呼它自己)
-
-[personality]
-prompt_personality = [
- "曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧",
- "是一个女大学生,你有黑色头发,你会刷小红书"
-]
-prompt_schedule = "一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书"
-
-[message]
-min_text_length = 2 # 最小回复长度
-max_context_size = 15 # 上下文记忆条数
-emoji_chance = 0.2 # 表情使用概率
-ban_words = [] # 禁用词列表
-
-[emoji]
-auto_save = true # 自动保存表情
-enable_check = false # 启用表情审核
-check_prompt = "符合公序良俗"
-
-[groups]
-talk_allowed = [] # 允许对话的群号
-talk_frequency_down = [] # 降低回复频率的群号
-ban_user_id = [] # 禁止回复的用户QQ号
-
-[others]
-enable_advance_output = true # 启用详细日志
-enable_kuuki_read = true # 启用场景理解
-
-# 模型配置
-[model.llm_reasoning] # 推理模型
-name = "Pro/deepseek-ai/DeepSeek-R1"
-base_url = "SILICONFLOW_BASE_URL"
-key = "SILICONFLOW_KEY"
-
-[model.llm_reasoning_minor] # 轻量推理模型
-name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
-base_url = "SILICONFLOW_BASE_URL"
-key = "SILICONFLOW_KEY"
-
-[model.llm_normal] # 对话模型
-name = "Pro/deepseek-ai/DeepSeek-V3"
-base_url = "SILICONFLOW_BASE_URL"
-key = "SILICONFLOW_KEY"
-
-[model.llm_normal_minor] # 备用对话模型
-name = "deepseek-ai/DeepSeek-V2.5"
-base_url = "SILICONFLOW_BASE_URL"
-key = "SILICONFLOW_KEY"
-
-[model.vlm] # 图像识别模型
-name = "deepseek-ai/deepseek-vl2"
-base_url = "SILICONFLOW_BASE_URL"
-key = "SILICONFLOW_KEY"
-
-[model.embedding] # 文本向量模型
-name = "BAAI/bge-m3"
-base_url = "SILICONFLOW_BASE_URL"
-key = "SILICONFLOW_KEY"
-
-
-[topic.llm_topic]
-name = "Pro/deepseek-ai/DeepSeek-V3"
-base_url = "SILICONFLOW_BASE_URL"
-key = "SILICONFLOW_KEY"
-```
**step # 6** 运行
@@ -438,7 +320,7 @@ sudo systemctl enable bot.service # 启动bot服务
sudo systemctl status bot.service # 检查bot服务状态
```
-```
-python bot.py
+```bash
+python bot.py # 运行麦麦
```
diff --git a/docs/manual_deploy_linux.md b/docs/manual_deploy_linux.md
index a5c91d6e2..653284bf5 100644
--- a/docs/manual_deploy_linux.md
+++ b/docs/manual_deploy_linux.md
@@ -6,7 +6,7 @@
- QQ小号(QQ框架的使用可能导致qq被风控,严重(小概率)可能会导致账号封禁,强烈不推荐使用大号)
- 可用的大模型API
- 一个AI助手,网上随便搜一家打开来用都行,可以帮你解决一些不懂的问题
-- 以下内容假设你对Linux系统有一定的了解,如果觉得难以理解,请直接用Windows系统部署[Windows系统部署指南](./manual_deploy_windows.md)
+- 以下内容假设你对Linux系统有一定的了解,如果觉得难以理解,请直接用Windows系统部署[Windows系统部署指南](./manual_deploy_windows.md)或[使用Windows一键包部署](https://github.com/MaiM-with-u/MaiBot/releases/tag/EasyInstall-windows)
## 你需要知道什么?
@@ -24,6 +24,9 @@
---
+## 一键部署
+请下载并运行项目根目录中的run.sh并按照提示安装,部署完成后请参照后续配置指南进行配置
+
## 环境配置
### 1️⃣ **确认Python版本**
@@ -36,17 +39,26 @@ python --version
python3 --version
```
-如果版本低于3.9,请更新Python版本。
+如果版本低于3.9,请更新Python版本,目前建议使用python3.12
```bash
-# Ubuntu/Debian
+# 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 apt install python3.12
+# Ubuntu
+sudo add-apt-repository ppa:deadsnakes/ppa
+sudo apt update
+sudo apt install python3.12
+
+# 执行完以上命令后,建议在执行时将python3指向python3.12
+# 更新替代方案,设置 python3.12 为默认的 python3 版本:
+sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
sudo update-alternatives --config python3
```
+建议再执行以下命令,使后续运行命令中的`python3`等同于`python`
+```bash
+sudo apt install python-is-python3
+```
### 2️⃣ **创建虚拟环境**
@@ -73,7 +85,7 @@ 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/)
+- 安装与启动:请参考[官方文档](https://www.mongodb.com/zh-cn/docs/manual/administration/install-on-linux/#std-label-install-mdb-community-edition-linux),进入后选择自己的系统版本即可
- 默认连接本地27017端口
---
@@ -82,7 +94,11 @@ pip install -r requirements.txt
### 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)安装
+- 执行NapCat的Linux一键使用脚本(支持Ubuntu 20+/Debian 10+/Centos9)
+```bash
+curl -o napcat.sh https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh && sudo bash napcat.sh
+```
+- 如果你不想使用Napcat的脚本安装,可参考[Napcat-Linux手动安装](https://www.napcat.wiki/guide/boot/Shell-Linux-SemiAuto)
- 使用QQ小号登录,添加反向WS地址: `ws://127.0.0.1:8080/onebot/v11/ws`
@@ -91,9 +107,17 @@ pip install -r requirements.txt
## 配置文件设置
### 5️⃣ **配置文件设置,让麦麦Bot正常工作**
-
-- 修改环境配置文件:`.env.prod`
-- 修改机器人配置文件:`bot_config.toml`
+可先运行一次
+```bash
+# 在项目目录下操作
+nb run
+# 或
+python3 bot.py
+```
+之后你就可以找到`.env.prod`和`bot_config.toml`这两个文件了
+关于文件内容的配置请参考:
+- [🎀 新手配置指南](./installation_cute.md) - 通俗易懂的配置教程,适合初次使用的猫娘
+- [⚙️ 标准配置指南](./installation_standard.md) - 简明专业的配置说明,适合有经验的用户
---
diff --git a/docs/manual_deploy_macos.md b/docs/manual_deploy_macos.md
new file mode 100644
index 000000000..00e2686b3
--- /dev/null
+++ b/docs/manual_deploy_macos.md
@@ -0,0 +1,201 @@
+# 📦 macOS系统手动部署MaiMbot麦麦指南
+
+## 准备工作
+
+- 一台搭载了macOS系统的设备(macOS 12.0 或以上)
+- QQ小号(QQ框架的使用可能导致qq被风控,严重(小概率)可能会导致账号封禁,强烈不推荐使用大号)
+- Homebrew包管理器
+ - 如未安装,你可以在https://github.com/Homebrew/brew/releases/latest 找到.pkg格式的安装包
+- 可用的大模型API
+- 一个AI助手,网上随便搜一家打开来用都行,可以帮你解决一些不懂的问题
+- 以下内容假设你对macOS系统有一定的了解,如果觉得难以理解,请直接用Windows系统部署[Windows系统部署指南](./manual_deploy_windows.md)或[使用Windows一键包部署](https://github.com/MaiM-with-u/MaiBot/releases/tag/EasyInstall-windows)
+- 终端应用(iTerm2等)
+
+---
+
+## 环境配置
+
+### 1️⃣ **Python环境配置**
+
+```bash
+# 检查Python版本(macOS自带python可能为2.7)
+python3 --version
+
+# 通过Homebrew安装Python
+brew install python@3.12
+
+# 设置环境变量(如使用zsh)
+echo 'export PATH="/usr/local/opt/python@3.12/bin:$PATH"' >> ~/.zshrc
+source ~/.zshrc
+
+# 验证安装
+python3 --version # 应显示3.12.x
+pip3 --version # 应关联3.12版本
+```
+
+### 2️⃣ **创建虚拟环境**
+
+```bash
+# 方法1:使用venv(推荐)
+python3 -m venv maimbot-venv
+source maimbot-venv/bin/activate # 激活虚拟环境
+
+# 方法2:使用conda
+brew install --cask miniconda
+conda create -n maimbot python=3.9
+conda activate maimbot # 激活虚拟环境
+
+# 安装项目依赖
+# 请确保已经进入虚拟环境再执行
+pip install -r requirements.txt
+```
+
+---
+
+## 数据库配置
+
+### 3️⃣ **安装MongoDB**
+
+请参考[官方文档](https://www.mongodb.com/zh-cn/docs/manual/tutorial/install-mongodb-on-os-x/#install-mongodb-community-edition)
+
+---
+
+## NapCat
+
+### 4️⃣ **安装与配置Napcat**
+- 安装
+可以使用Napcat官方提供的[macOS安装工具](https://github.com/NapNeko/NapCat-Mac-Installer/releases/)
+由于权限问题,补丁过程需要手动替换 package.json,请注意备份原文件~
+- 配置
+使用QQ小号登录,添加反向WS地址: `ws://127.0.0.1:8080/onebot/v11/ws`
+
+---
+
+## 配置文件设置
+
+### 5️⃣ **生成配置文件**
+可先运行一次
+```bash
+# 在项目目录下操作
+nb run
+# 或
+python3 bot.py
+```
+
+之后你就可以找到`.env.prod`和`bot_config.toml`这两个文件了
+
+关于文件内容的配置请参考:
+- [🎀 新手配置指南](./installation_cute.md) - 通俗易懂的配置教程,适合初次使用的猫娘
+- [⚙️ 标准配置指南](./installation_standard.md) - 简明专业的配置说明,适合有经验的用户
+
+
+---
+
+## 启动机器人
+
+### 6️⃣ **启动麦麦机器人**
+
+```bash
+# 在项目目录下操作
+nb run
+# 或
+python3 bot.py
+```
+
+## 启动管理
+
+### 7️⃣ **通过launchd管理服务**
+
+创建plist文件:
+
+```bash
+nano ~/Library/LaunchAgents/com.maimbot.plist
+```
+
+内容示例(需替换实际路径):
+
+```xml
+
+
+
+
+ Label
+ com.maimbot
+
+ ProgramArguments
+
+ /path/to/maimbot-venv/bin/python
+ /path/to/MaiMbot/bot.py
+
+
+ WorkingDirectory
+ /path/to/MaiMbot
+
+ StandardOutPath
+ /tmp/maimbot.log
+ StandardErrorPath
+ /tmp/maimbot.err
+
+ RunAtLoad
+
+ KeepAlive
+
+
+
+```
+
+加载服务:
+
+```bash
+launchctl load ~/Library/LaunchAgents/com.maimbot.plist
+launchctl start com.maimbot
+```
+
+查看日志:
+
+```bash
+tail -f /tmp/maimbot.log
+```
+
+---
+
+## 常见问题处理
+
+1. **权限问题**
+```bash
+# 遇到文件权限错误时
+chmod -R 755 ~/Documents/MaiMbot
+```
+
+2. **Python模块缺失**
+```bash
+# 确保在虚拟环境中
+source maimbot-venv/bin/activate # 或 conda 激活
+pip install --force-reinstall -r requirements.txt
+```
+
+3. **MongoDB连接失败**
+```bash
+# 检查服务状态
+brew services list
+# 重置数据库权限
+mongosh --eval "db.adminCommand({setFeatureCompatibilityVersion: '5.0'})"
+```
+
+---
+
+## 系统优化建议
+
+1. **关闭App Nap**
+```bash
+# 防止系统休眠NapCat进程
+defaults write NSGlobalDomain NSAppSleepDisabled -bool YES
+```
+
+2. **电源管理设置**
+```bash
+# 防止睡眠影响机器人运行
+sudo systemsetup -setcomputersleep Never
+```
+
+---
diff --git a/docs/API_KEY.png b/docs/pic/API_KEY.png
similarity index 100%
rename from docs/API_KEY.png
rename to docs/pic/API_KEY.png
diff --git a/docs/MONGO_DB_0.png b/docs/pic/MONGO_DB_0.png
similarity index 100%
rename from docs/MONGO_DB_0.png
rename to docs/pic/MONGO_DB_0.png
diff --git a/docs/MONGO_DB_1.png b/docs/pic/MONGO_DB_1.png
similarity index 100%
rename from docs/MONGO_DB_1.png
rename to docs/pic/MONGO_DB_1.png
diff --git a/docs/MONGO_DB_2.png b/docs/pic/MONGO_DB_2.png
similarity index 100%
rename from docs/MONGO_DB_2.png
rename to docs/pic/MONGO_DB_2.png
diff --git a/docs/pic/MongoDB_Ubuntu_guide.png b/docs/pic/MongoDB_Ubuntu_guide.png
new file mode 100644
index 000000000..abd47c283
Binary files /dev/null and b/docs/pic/MongoDB_Ubuntu_guide.png differ
diff --git a/docs/pic/QQ_Download_guide_Linux.png b/docs/pic/QQ_Download_guide_Linux.png
new file mode 100644
index 000000000..1d47e9d27
Binary files /dev/null and b/docs/pic/QQ_Download_guide_Linux.png differ
diff --git a/docs/pic/linux_beginner_downloadguide.png b/docs/pic/linux_beginner_downloadguide.png
new file mode 100644
index 000000000..4c6fbf01b
Binary files /dev/null and b/docs/pic/linux_beginner_downloadguide.png differ
diff --git a/docs/synology_.env.prod.png b/docs/pic/synology_.env.prod.png
similarity index 100%
rename from docs/synology_.env.prod.png
rename to docs/pic/synology_.env.prod.png
diff --git a/docs/synology_create_project.png b/docs/pic/synology_create_project.png
similarity index 100%
rename from docs/synology_create_project.png
rename to docs/pic/synology_create_project.png
diff --git a/docs/synology_docker-compose.png b/docs/pic/synology_docker-compose.png
similarity index 100%
rename from docs/synology_docker-compose.png
rename to docs/pic/synology_docker-compose.png
diff --git a/docs/synology_how_to_download.png b/docs/pic/synology_how_to_download.png
similarity index 100%
rename from docs/synology_how_to_download.png
rename to docs/pic/synology_how_to_download.png
diff --git a/docs/video.png b/docs/pic/video.png
similarity index 100%
rename from docs/video.png
rename to docs/pic/video.png
diff --git a/docs/synology_deploy.md b/docs/synology_deploy.md
index a7b3bebda..1139101ec 100644
--- a/docs/synology_deploy.md
+++ b/docs/synology_deploy.md
@@ -16,7 +16,7 @@
docker-compose.yml: https://github.com/SengokuCola/MaiMBot/blob/main/docker-compose.yml
下载后打开,将 `services-mongodb-image` 修改为 `mongo:4.4.24`。这是因为最新的 MongoDB 强制要求 AVX 指令集,而群晖似乎不支持这个指令集
-
+
bot_config.toml: https://github.com/SengokuCola/MaiMBot/blob/main/template/bot_config_template.toml
下载后,重命名为 `bot_config.toml`
@@ -26,13 +26,13 @@ bot_config.toml: https://github.com/SengokuCola/MaiMBot/blob/main/template/bot_c
下载后,重命名为 `.env.prod`
将 `HOST` 修改为 `0.0.0.0`,确保 maimbot 能被 napcat 访问
按下图修改 mongodb 设置,使用 `MONGODB_URI`
-
+
把 `bot_config.toml` 和 `.env.prod` 放入之前创建的 `MaiMBot`文件夹
#### 如何下载?
-点这里!
+点这里!
### 创建项目
@@ -45,7 +45,7 @@ bot_config.toml: https://github.com/SengokuCola/MaiMBot/blob/main/template/bot_c
图例:
-
+
一路点下一步,等待项目创建完成
diff --git a/src/plugins/chat/__init__.py b/src/plugins/chat/__init__.py
index e73d0a230..7edf91558 100644
--- a/src/plugins/chat/__init__.py
+++ b/src/plugins/chat/__init__.py
@@ -92,12 +92,13 @@ async def _(bot: Bot):
@msg_in.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
- #处理合并转发消息
+ # 处理合并转发消息
if "forward" in event.message:
- await chat_bot.handle_forward_message(event , bot)
- else :
+ await chat_bot.handle_forward_message(event, bot)
+ else:
await chat_bot.handle_message(event, bot)
+
@notice_matcher.handle()
async def _(bot: Bot, event: NoticeEvent, state: T_State):
logger.debug(f"收到通知:{event}")
diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py
index d30940f97..24b7bdbff 100644
--- a/src/plugins/chat/bot.py
+++ b/src/plugins/chat/bot.py
@@ -418,13 +418,12 @@ class ChatBot:
# 用户屏蔽,不区分私聊/群聊
if event.user_id in global_config.ban_user_id:
return
-
+
if isinstance(event, GroupMessageEvent):
if event.group_id:
if event.group_id not in global_config.talk_allowed_groups:
return
-
# 获取合并转发消息的详细信息
forward_info = await bot.get_forward_msg(message_id=event.message_id)
messages = forward_info["messages"]
@@ -434,17 +433,17 @@ class ChatBot:
for node in messages:
# 提取发送者昵称
nickname = node["sender"].get("nickname", "未知用户")
-
+
# 递归处理消息内容
- message_content = await self.process_message_segments(node["message"],layer=0)
-
+ message_content = await self.process_message_segments(node["message"], layer=0)
+
# 拼接为【昵称】+ 内容
processed_messages.append(f"【{nickname}】{message_content}")
# 组合所有消息
combined_message = "\n".join(processed_messages)
combined_message = f"合并转发消息内容:\n{combined_message}"
-
+
# 构建用户信息(使用转发消息的发送者)
user_info = UserInfo(
user_id=event.user_id,
@@ -456,11 +455,7 @@ class ChatBot:
# 构建群聊信息(如果是群聊)
group_info = None
if isinstance(event, GroupMessageEvent):
- group_info = GroupInfo(
- group_id=event.group_id,
- group_name=None,
- platform="qq"
- )
+ group_info = GroupInfo(group_id=event.group_id, group_name=None, platform="qq")
# 创建消息对象
message_cq = MessageRecvCQ(
@@ -475,19 +470,19 @@ class ChatBot:
# 进入标准消息处理流程
await self.message_process(message_cq)
- async def process_message_segments(self, segments: list,layer:int) -> str:
+ async def process_message_segments(self, segments: list, layer: int) -> str:
"""递归处理消息段"""
parts = []
for seg in segments:
- part = await self.process_segment(seg,layer+1)
+ part = await self.process_segment(seg, layer + 1)
parts.append(part)
return "".join(parts)
- async def process_segment(self, seg: dict , layer:int) -> str:
+ async def process_segment(self, seg: dict, layer: int) -> str:
"""处理单个消息段"""
seg_type = seg["type"]
- if layer > 3 :
- #防止有那种100层转发消息炸飞麦麦
+ if layer > 3:
+ # 防止有那种100层转发消息炸飞麦麦
return "【转发消息】"
if seg_type == "text":
return seg["data"]["text"]
@@ -504,13 +499,14 @@ class ChatBot:
nested_messages.append("合并转发消息内容:")
for node in nested_nodes:
nickname = node["sender"].get("nickname", "未知用户")
- content = await self.process_message_segments(node["message"],layer=layer)
+ content = await self.process_message_segments(node["message"], layer=layer)
# nested_messages.append('-' * layer)
nested_messages.append(f"{'--' * layer}【{nickname}】{content}")
# nested_messages.append(f"{'--' * layer}合并转发第【{layer}】层结束")
return "\n".join(nested_messages)
else:
return f"[{seg_type}]"
-
+
+
# 创建全局ChatBot实例
chat_bot = ChatBot()
diff --git a/src/plugins/personality/big5_test.py b/src/plugins/personality/big5_test.py
index 80114ec36..c66e6ec4e 100644
--- a/src/plugins/personality/big5_test.py
+++ b/src/plugins/personality/big5_test.py
@@ -15,17 +15,14 @@ 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,get_all_scenes,PERSONALITY_SCENES
-from src.plugins.personality.questionnaire import PERSONALITY_QUESTIONS,FACTOR_DESCRIPTIONS
-from src.plugins.personality.offline_llm import LLMModel
-
+from src.plugins.personality.questionnaire import PERSONALITY_QUESTIONS, FACTOR_DESCRIPTIONS # noqa: E402
class BigFiveTest:
def __init__(self):
self.questions = PERSONALITY_QUESTIONS
self.factors = FACTOR_DESCRIPTIONS
-
+
def run_test(self):
"""运行测试并收集答案"""
print("\n欢迎参加中国大五人格测试!")
@@ -37,17 +34,17 @@ class BigFiveTest:
print("5 = 比较符合")
print("6 = 完全符合")
print("\n请认真阅读每个描述,选择最符合您实际情况的选项。\n")
-
+
# 创建题目序号到题目的映射
- questions_map = {q['id']: q for q in self.questions}
-
+ questions_map = {q["id"]: q for q in self.questions}
+
# 获取所有题目ID并随机打乱顺序
question_ids = list(questions_map.keys())
random.shuffle(question_ids)
-
+
answers = {}
total_questions = len(question_ids)
-
+
for i, question_id in enumerate(question_ids, 1):
question = questions_map[question_id]
while True:
@@ -61,52 +58,43 @@ class BigFiveTest:
print("请输入1-6之间的数字!")
except ValueError:
print("请输入有效的数字!")
-
+
return self.calculate_scores(answers)
-
+
def calculate_scores(self, answers):
"""计算各维度得分"""
results = {}
- factor_questions = {
- "外向性": [],
- "神经质": [],
- "严谨性": [],
- "开放性": [],
- "宜人性": []
- }
-
+ factor_questions = {"外向性": [], "神经质": [], "严谨性": [], "开放性": [], "宜人性": []}
+
# 将题目按因子分类
for q in self.questions:
- factor_questions[q['factor']].append(q)
-
+ factor_questions[q["factor"]].append(q)
+
# 计算每个维度的得分
for factor, questions in factor_questions.items():
total_score = 0
for q in questions:
- score = answers[q['id']]
+ score = answers[q["id"]]
# 处理反向计分题目
- if q['reverse_scoring']:
+ if q["reverse_scoring"]:
score = 7 - score # 6分量表反向计分为7减原始分
total_score += score
-
+
# 计算平均分
avg_score = round(total_score / len(questions), 2)
- results[factor] = {
- "得分": avg_score,
- "题目数": len(questions),
- "总分": total_score
- }
-
+ results[factor] = {"得分": avg_score, "题目数": len(questions), "总分": total_score}
+
return results
def get_factor_description(self, factor):
"""获取因子的详细描述"""
return self.factors[factor]
+
def main():
test = BigFiveTest()
results = test.run_test()
-
+
print("\n测试结果:")
print("=" * 50)
for factor, data in results.items():
@@ -114,9 +102,10 @@ def main():
print(f"平均分: {data['得分']} (总分: {data['总分']}, 题目数: {data['题目数']})")
print("-" * 30)
description = test.get_factor_description(factor)
- print("维度说明:", description['description'][:100] + "...")
- print("\n特征词:", ", ".join(description['trait_words']))
+ print("维度说明:", description["description"][:100] + "...")
+ print("\n特征词:", ", ".join(description["trait_words"]))
print("=" * 50)
-
+
+
if __name__ == "__main__":
main()
diff --git a/src/plugins/personality/combined_test.py b/src/plugins/personality/combined_test.py
index a842847fb..b08fb458a 100644
--- a/src/plugins/personality/combined_test.py
+++ b/src/plugins/personality/combined_test.py
@@ -1,4 +1,4 @@
-from typing import Dict, List
+from typing import Dict
import json
import os
from pathlib import Path
@@ -14,16 +14,17 @@ 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.big5_test import BigFiveTest
-from src.plugins.personality.renqingziji import PersonalityEvaluator_direct
-from src.plugins.personality.questionnaire import FACTOR_DESCRIPTIONS, PERSONALITY_QUESTIONS
+from src.plugins.personality.big5_test import BigFiveTest # noqa: E402
+from src.plugins.personality.renqingziji import PersonalityEvaluator_direct # noqa: E402
+from src.plugins.personality.questionnaire import FACTOR_DESCRIPTIONS, PERSONALITY_QUESTIONS # noqa: E402
+
class CombinedPersonalityTest:
def __init__(self):
self.big5_test = BigFiveTest()
self.scenario_test = PersonalityEvaluator_direct()
self.dimensions = ["开放性", "严谨性", "外向性", "宜人性", "神经质"]
-
+
def run_combined_test(self):
"""运行组合测试"""
print("\n=== 人格特征综合评估系统 ===")
@@ -32,12 +33,12 @@ class CombinedPersonalityTest:
print("2. 情景反应测评(15个场景)")
print("\n两种测评完成后,将对比分析结果的异同。")
input("\n准备好开始第一部分(问卷测评)了吗?按回车继续...")
-
+
# 运行问卷测试
print("\n=== 第一部分:问卷测评 ===")
print("本部分采用六级评分,请根据每个描述与您的符合程度进行打分:")
print("1 = 完全不符合")
- print("2 = 比较不符合")
+ print("2 = 比较不符合")
print("3 = 有点不符合")
print("4 = 有点符合")
print("5 = 比较符合")
@@ -47,42 +48,39 @@ class CombinedPersonalityTest:
print("2. 根据您想要扮演的角色特征来回答")
print("\n无论选择哪种方式,请保持一致并认真回答每个问题。")
input("\n按回车开始答题...")
-
+
questionnaire_results = self.run_questionnaire()
-
+
# 转换问卷结果格式以便比较
- questionnaire_scores = {
- factor: data["得分"]
- for factor, data in questionnaire_results.items()
- }
-
+ questionnaire_scores = {factor: data["得分"] for factor, data in questionnaire_results.items()}
+
# 运行情景测试
print("\n=== 第二部分:情景反应测评 ===")
print("接下来,您将面对一系列具体场景,请描述您在每个场景中可能的反应。")
print("每个场景都会评估不同的人格维度,共15个场景。")
print("您可以选择提供自己的真实反应,也可以选择扮演一个您创作的角色来回答。")
input("\n准备好开始了吗?按回车继续...")
-
+
scenario_results = self.run_scenario_test()
-
+
# 比较和展示结果
self.compare_and_display_results(questionnaire_scores, scenario_results)
-
+
# 保存结果
self.save_results(questionnaire_scores, scenario_results)
def run_questionnaire(self):
"""运行问卷测试部分"""
# 创建题目序号到题目的映射
- questions_map = {q['id']: q for q in PERSONALITY_QUESTIONS}
-
+ questions_map = {q["id"]: q for q in PERSONALITY_QUESTIONS}
+
# 获取所有题目ID并随机打乱顺序
question_ids = list(questions_map.keys())
random.shuffle(question_ids)
-
+
answers = {}
total_questions = len(question_ids)
-
+
for i, question_id in enumerate(question_ids, 1):
question = questions_map[question_id]
while True:
@@ -97,48 +95,38 @@ class CombinedPersonalityTest:
print("请输入1-6之间的数字!")
except ValueError:
print("请输入有效的数字!")
-
+
# 每10题显示一次进度
if i % 10 == 0:
- print(f"\n已完成 {i}/{total_questions} 题 ({int(i/total_questions*100)}%)")
-
+ print(f"\n已完成 {i}/{total_questions} 题 ({int(i / total_questions * 100)}%)")
+
return self.calculate_questionnaire_scores(answers)
-
+
def calculate_questionnaire_scores(self, answers):
"""计算问卷测试的维度得分"""
results = {}
- factor_questions = {
- "外向性": [],
- "神经质": [],
- "严谨性": [],
- "开放性": [],
- "宜人性": []
- }
-
+ factor_questions = {"外向性": [], "神经质": [], "严谨性": [], "开放性": [], "宜人性": []}
+
# 将题目按因子分类
for q in PERSONALITY_QUESTIONS:
- factor_questions[q['factor']].append(q)
-
+ factor_questions[q["factor"]].append(q)
+
# 计算每个维度的得分
for factor, questions in factor_questions.items():
total_score = 0
for q in questions:
- score = answers[q['id']]
+ score = answers[q["id"]]
# 处理反向计分题目
- if q['reverse_scoring']:
+ if q["reverse_scoring"]:
score = 7 - score # 6分量表反向计分为7减原始分
total_score += score
-
+
# 计算平均分
avg_score = round(total_score / len(questions), 2)
- results[factor] = {
- "得分": avg_score,
- "题目数": len(questions),
- "总分": total_score
- }
-
+ results[factor] = {"得分": avg_score, "题目数": len(questions), "总分": total_score}
+
return results
-
+
def run_scenario_test(self):
"""运行情景测试部分"""
final_scores = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0}
@@ -160,11 +148,7 @@ class CombinedPersonalityTest:
continue
print("\n正在评估您的描述...")
- scores = self.scenario_test.evaluate_response(
- scenario_data["场景"],
- response,
- scenario_data["评估维度"]
- )
+ scores = self.scenario_test.evaluate_response(scenario_data["场景"], response, scenario_data["评估维度"])
# 更新分数
for dimension, score in scores.items():
@@ -178,7 +162,7 @@ class CombinedPersonalityTest:
# 每5个场景显示一次总进度
if i % 5 == 0:
- print(f"\n已完成 {i}/{len(scenarios)} 个场景 ({int(i/len(scenarios)*100)}%)")
+ print(f"\n已完成 {i}/{len(scenarios)} 个场景 ({int(i / len(scenarios) * 100)}%)")
if i < len(scenarios):
input("\n按回车继续下一个场景...")
@@ -186,11 +170,8 @@ class CombinedPersonalityTest:
# 计算平均分
for dimension in final_scores:
if dimension_counts[dimension] > 0:
- final_scores[dimension] = round(
- final_scores[dimension] / dimension_counts[dimension],
- 2
- )
-
+ final_scores[dimension] = round(final_scores[dimension] / dimension_counts[dimension], 2)
+
return final_scores
def compare_and_display_results(self, questionnaire_scores: Dict, scenario_scores: Dict):
@@ -199,39 +180,43 @@ class CombinedPersonalityTest:
print("\n" + "=" * 60)
print(f"{'维度':<8} {'问卷得分':>10} {'情景得分':>10} {'差异':>10} {'差异程度':>10}")
print("-" * 60)
-
+
# 收集每个维度的得分用于统计分析
questionnaire_values = []
scenario_values = []
diffs = []
-
+
for dimension in self.dimensions:
q_score = questionnaire_scores[dimension]
s_score = scenario_scores[dimension]
diff = round(abs(q_score - s_score), 2)
-
+
questionnaire_values.append(q_score)
scenario_values.append(s_score)
diffs.append(diff)
-
+
# 计算差异程度
diff_level = "低" if diff < 0.5 else "中" if diff < 1.0 else "高"
print(f"{dimension:<8} {q_score:>10.2f} {s_score:>10.2f} {diff:>10.2f} {diff_level:>10}")
-
+
print("=" * 60)
-
+
# 计算整体统计指标
mean_diff = sum(diffs) / len(diffs)
std_diff = (sum((x - mean_diff) ** 2 for x in diffs) / (len(diffs) - 1)) ** 0.5
-
+
# 计算效应量 (Cohen's d)
- pooled_std = ((sum((x - sum(questionnaire_values)/len(questionnaire_values))**2 for x in questionnaire_values) +
- sum((x - sum(scenario_values)/len(scenario_values))**2 for x in scenario_values)) /
- (2 * len(self.dimensions) - 2)) ** 0.5
-
+ pooled_std = (
+ (
+ sum((x - sum(questionnaire_values) / len(questionnaire_values)) ** 2 for x in questionnaire_values)
+ + sum((x - sum(scenario_values) / len(scenario_values)) ** 2 for x in scenario_values)
+ )
+ / (2 * len(self.dimensions) - 2)
+ ) ** 0.5
+
if pooled_std != 0:
cohens_d = abs(mean_diff / pooled_std)
-
+
# 解释效应量
if cohens_d < 0.2:
effect_size = "微小"
@@ -241,41 +226,43 @@ class CombinedPersonalityTest:
effect_size = "中等"
else:
effect_size = "大"
-
+
# 对所有维度进行整体t检验
t_stat, p_value = stats.ttest_rel(questionnaire_values, scenario_values)
- print(f"\n整体统计分析:")
+ print("\n整体统计分析:")
print(f"平均差异: {mean_diff:.3f}")
print(f"差异标准差: {std_diff:.3f}")
print(f"效应量(Cohen's d): {cohens_d:.3f}")
print(f"效应量大小: {effect_size}")
print(f"t统计量: {t_stat:.3f}")
print(f"p值: {p_value:.3f}")
-
+
if p_value < 0.05:
print("结论: 两种测评方法的结果存在显著差异 (p < 0.05)")
else:
print("结论: 两种测评方法的结果无显著差异 (p >= 0.05)")
-
+
print("\n维度说明:")
for dimension in self.dimensions:
print(f"\n{dimension}:")
desc = FACTOR_DESCRIPTIONS[dimension]
print(f"定义:{desc['description']}")
print(f"特征词:{', '.join(desc['trait_words'])}")
-
+
# 分析显著差异
significant_diffs = []
for dimension in self.dimensions:
diff = abs(questionnaire_scores[dimension] - scenario_scores[dimension])
if diff >= 1.0: # 差异大于等于1分视为显著
- significant_diffs.append({
- "dimension": dimension,
- "diff": diff,
- "questionnaire": questionnaire_scores[dimension],
- "scenario": scenario_scores[dimension]
- })
-
+ significant_diffs.append(
+ {
+ "dimension": dimension,
+ "diff": diff,
+ "questionnaire": questionnaire_scores[dimension],
+ "scenario": scenario_scores[dimension],
+ }
+ )
+
if significant_diffs:
print("\n\n显著差异分析:")
print("-" * 40)
@@ -284,9 +271,9 @@ class CombinedPersonalityTest:
print(f"问卷得分:{diff['questionnaire']:.2f}")
print(f"情景得分:{diff['scenario']:.2f}")
print(f"差异值:{diff['diff']:.2f}")
-
+
# 分析可能的原因
- if diff['questionnaire'] > diff['scenario']:
+ if diff["questionnaire"] > diff["scenario"]:
print("可能原因:在问卷中的自我评价较高,但在具体情景中的表现较为保守。")
else:
print("可能原因:在具体情景中表现出更多该维度特征,而在问卷自评时较为保守。")
@@ -297,38 +284,37 @@ class CombinedPersonalityTest:
"测试时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"问卷测评结果": questionnaire_scores,
"情景测评结果": scenario_scores,
- "维度说明": FACTOR_DESCRIPTIONS
+ "维度说明": FACTOR_DESCRIPTIONS,
}
-
+
# 确保目录存在
os.makedirs("results", exist_ok=True)
-
+
# 生成带时间戳的文件名
filename = f"results/personality_combined_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
-
+
# 保存到文件
with open(filename, "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
-
+
print(f"\n完整的测评结果已保存到:{filename}")
+
def load_existing_results():
"""检查并加载已有的测试结果"""
results_dir = "results"
if not os.path.exists(results_dir):
return None
-
+
# 获取所有personality_combined开头的文件
- result_files = [f for f in os.listdir(results_dir)
- if f.startswith("personality_combined_") and f.endswith(".json")]
-
+ result_files = [f for f in os.listdir(results_dir) if f.startswith("personality_combined_") and f.endswith(".json")]
+
if not result_files:
return None
-
+
# 按文件修改时间排序,获取最新的结果文件
- latest_file = max(result_files,
- key=lambda f: os.path.getmtime(os.path.join(results_dir, f)))
-
+ latest_file = max(result_files, key=lambda f: os.path.getmtime(os.path.join(results_dir, f)))
+
print(f"\n发现已有的测试结果:{latest_file}")
try:
with open(os.path.join(results_dir, latest_file), "r", encoding="utf-8") as f:
@@ -338,24 +324,26 @@ def load_existing_results():
print(f"读取结果文件时出错:{str(e)}")
return None
+
def main():
test = CombinedPersonalityTest()
-
+
# 检查是否存在已有结果
existing_results = load_existing_results()
-
+
if existing_results:
print("\n=== 使用已有测试结果进行分析 ===")
print(f"测试时间:{existing_results['测试时间']}")
-
+
questionnaire_scores = existing_results["问卷测评结果"]
scenario_scores = existing_results["情景测评结果"]
-
+
# 直接进行结果对比分析
test.compare_and_display_results(questionnaire_scores, scenario_scores)
else:
print("\n未找到已有的测试结果,开始新的测试...")
test.run_combined_test()
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/src/plugins/personality/questionnaire.py b/src/plugins/personality/questionnaire.py
index 4afff1185..8e965061d 100644
--- a/src/plugins/personality/questionnaire.py
+++ b/src/plugins/personality/questionnaire.py
@@ -1,5 +1,9 @@
-# 人格测试问卷题目 王孟成, 戴晓阳, & 姚树桥. (2011). 中国大五人格问卷的初步编制Ⅲ:简式版的制定及信效度检验. 中国临床心理学杂志, 19(04), Article 04.
-# 王孟成, 戴晓阳, & 姚树桥. (2010). 中国大五人格问卷的初步编制Ⅰ:理论框架与信度分析. 中国临床心理学杂志, 18(05), Article 05.
+# 人格测试问卷题目
+# 王孟成, 戴晓阳, & 姚树桥. (2011).
+# 中国大五人格问卷的初步编制Ⅲ:简式版的制定及信效度检验. 中国临床心理学杂志, 19(04), Article 04.
+
+# 王孟成, 戴晓阳, & 姚树桥. (2010).
+# 中国大五人格问卷的初步编制Ⅰ:理论框架与信度分析. 中国临床心理学杂志, 18(05), Article 05.
PERSONALITY_QUESTIONS = [
# 神经质维度 (F1)
@@ -11,7 +15,6 @@ PERSONALITY_QUESTIONS = [
{"id": 6, "content": "在面对压力时,我有种快要崩溃的感觉", "factor": "神经质", "reverse_scoring": False},
{"id": 7, "content": "我常担忧一些无关紧要的事情", "factor": "神经质", "reverse_scoring": False},
{"id": 8, "content": "我常常感到内心不踏实", "factor": "神经质", "reverse_scoring": False},
-
# 严谨性维度 (F2)
{"id": 9, "content": "在工作上,我常只求能应付过去便可", "factor": "严谨性", "reverse_scoring": True},
{"id": 10, "content": "一旦确定了目标,我会坚持努力地实现它", "factor": "严谨性", "reverse_scoring": False},
@@ -21,9 +24,13 @@ PERSONALITY_QUESTIONS = [
{"id": 14, "content": "我喜欢一开头就把事情计划好", "factor": "严谨性", "reverse_scoring": False},
{"id": 15, "content": "我工作或学习很勤奋", "factor": "严谨性", "reverse_scoring": False},
{"id": 16, "content": "我是个倾尽全力做事的人", "factor": "严谨性", "reverse_scoring": False},
-
# 宜人性维度 (F3)
- {"id": 17, "content": "尽管人类社会存在着一些阴暗的东西(如战争、罪恶、欺诈),我仍然相信人性总的来说是善良的", "factor": "宜人性", "reverse_scoring": False},
+ {
+ "id": 17,
+ "content": "尽管人类社会存在着一些阴暗的东西(如战争、罪恶、欺诈),我仍然相信人性总的来说是善良的",
+ "factor": "宜人性",
+ "reverse_scoring": False,
+ },
{"id": 18, "content": "我觉得大部分人基本上是心怀善意的", "factor": "宜人性", "reverse_scoring": False},
{"id": 19, "content": "虽然社会上有骗子,但我觉得大部分人还是可信的", "factor": "宜人性", "reverse_scoring": False},
{"id": 20, "content": "我不太关心别人是否受到不公正的待遇", "factor": "宜人性", "reverse_scoring": True},
@@ -31,7 +38,6 @@ PERSONALITY_QUESTIONS = [
{"id": 22, "content": "我常为那些遭遇不幸的人感到难过", "factor": "宜人性", "reverse_scoring": False},
{"id": 23, "content": "我是那种只照顾好自己,不替别人担忧的人", "factor": "宜人性", "reverse_scoring": True},
{"id": 24, "content": "当别人向我诉说不幸时,我常感到难过", "factor": "宜人性", "reverse_scoring": False},
-
# 开放性维度 (F4)
{"id": 25, "content": "我的想象力相当丰富", "factor": "开放性", "reverse_scoring": False},
{"id": 26, "content": "我头脑中经常充满生动的画面", "factor": "开放性", "reverse_scoring": False},
@@ -39,9 +45,18 @@ PERSONALITY_QUESTIONS = [
{"id": 28, "content": "我喜欢冒险", "factor": "开放性", "reverse_scoring": False},
{"id": 29, "content": "我是个勇于冒险,突破常规的人", "factor": "开放性", "reverse_scoring": False},
{"id": 30, "content": "我身上具有别人没有的冒险精神", "factor": "开放性", "reverse_scoring": False},
- {"id": 31, "content": "我渴望学习一些新东西,即使它们与我的日常生活无关", "factor": "开放性", "reverse_scoring": False},
- {"id": 32, "content": "我很愿意也很容易接受那些新事物、新观点、新想法", "factor": "开放性", "reverse_scoring": False},
-
+ {
+ "id": 31,
+ "content": "我渴望学习一些新东西,即使它们与我的日常生活无关",
+ "factor": "开放性",
+ "reverse_scoring": False,
+ },
+ {
+ "id": 32,
+ "content": "我很愿意也很容易接受那些新事物、新观点、新想法",
+ "factor": "开放性",
+ "reverse_scoring": False,
+ },
# 外向性维度 (F5)
{"id": 33, "content": "我喜欢参加社交与娱乐聚会", "factor": "外向性", "reverse_scoring": False},
{"id": 34, "content": "我对人多的聚会感到乏味", "factor": "外向性", "reverse_scoring": True},
@@ -50,61 +65,78 @@ PERSONALITY_QUESTIONS = [
{"id": 37, "content": "有我在的场合一般不会冷场", "factor": "外向性", "reverse_scoring": False},
{"id": 38, "content": "我希望成为领导者而不是被领导者", "factor": "外向性", "reverse_scoring": False},
{"id": 39, "content": "在一个团体中,我希望处于领导地位", "factor": "外向性", "reverse_scoring": False},
- {"id": 40, "content": "别人多认为我是一个热情和友好的人", "factor": "外向性", "reverse_scoring": False}
+ {"id": 40, "content": "别人多认为我是一个热情和友好的人", "factor": "外向性", "reverse_scoring": False},
]
# 因子维度说明
FACTOR_DESCRIPTIONS = {
"外向性": {
- "description": "反映个体神经系统的强弱和动力特征。外向性主要表现为个体在人际交往和社交活动中的倾向性,包括对社交活动的兴趣、对人群的态度、社交互动中的主动程度以及在群体中的影响力。高分者倾向于积极参与社交活动,乐于与人交往,善于表达自我,并往往在群体中发挥领导作用;低分者则倾向于独处,不喜欢热闹的社交场合,表现出内向、安静的特征。",
+ "description": "反映个体神经系统的强弱和动力特征。外向性主要表现为个体在人际交往和社交活动中的倾向性,"
+ "包括对社交活动的兴趣、"
+ "对人群的态度、社交互动中的主动程度以及在群体中的影响力。高分者倾向于积极参与社交活动,乐于与人交往,善于表达自我,"
+ "并往往在群体中发挥领导作用;低分者则倾向于独处,不喜欢热闹的社交场合,表现出内向、安静的特征。",
"trait_words": ["热情", "活力", "社交", "主动"],
"subfactors": {
"合群性": "个体愿意与他人聚在一起,即接近人群的倾向;高分表现乐群、好交际,低分表现封闭、独处",
"热情": "个体对待别人时所表现出的态度;高分表现热情好客,低分表现冷淡",
"支配性": "个体喜欢指使、操纵他人,倾向于领导别人的特点;高分表现好强、发号施令,低分表现顺从、低调",
- "活跃": "个体精力充沛,活跃、主动性等特点;高分表现活跃,低分表现安静"
- }
+ "活跃": "个体精力充沛,活跃、主动性等特点;高分表现活跃,低分表现安静",
+ },
},
"神经质": {
- "description": "反映个体情绪的状态和体验内心苦恼的倾向性。这个维度主要关注个体在面对压力、挫折和日常生活挑战时的情绪稳定性和适应能力。它包含了对焦虑、抑郁、愤怒等负面情绪的敏感程度,以及个体对这些情绪的调节和控制能力。高分者容易体验负面情绪,对压力较为敏感,情绪波动较大;低分者则表现出较强的情绪稳定性,能够较好地应对压力和挫折。",
+ "description": "反映个体情绪的状态和体验内心苦恼的倾向性。这个维度主要关注个体在面对压力、"
+ "挫折和日常生活挑战时的情绪稳定性和适应能力。它包含了对焦虑、抑郁、愤怒等负面情绪的敏感程度,"
+ "以及个体对这些情绪的调节和控制能力。高分者容易体验负面情绪,对压力较为敏感,情绪波动较大;"
+ "低分者则表现出较强的情绪稳定性,能够较好地应对压力和挫折。",
"trait_words": ["稳定", "沉着", "从容", "坚韧"],
"subfactors": {
"焦虑": "个体体验焦虑感的个体差异;高分表现坐立不安,低分表现平静",
"抑郁": "个体体验抑郁情感的个体差异;高分表现郁郁寡欢,低分表现平静",
- "敏感多疑": "个体常常关注自己的内心活动,行为和过于意识人对自己的看法、评价;高分表现敏感多疑,低分表现淡定、自信",
+ "敏感多疑": "个体常常关注自己的内心活动,行为和过于意识人对自己的看法、评价;高分表现敏感多疑,"
+ "低分表现淡定、自信",
"脆弱性": "个体在危机或困难面前无力、脆弱的特点;高分表现无能、易受伤、逃避,低分表现坚强",
- "愤怒-敌意": "个体准备体验愤怒,及相关情绪的状态;高分表现暴躁易怒,低分表现平静"
- }
+ "愤怒-敌意": "个体准备体验愤怒,及相关情绪的状态;高分表现暴躁易怒,低分表现平静",
+ },
},
"严谨性": {
- "description": "反映个体在目标导向行为上的组织、坚持和动机特征。这个维度体现了个体在工作、学习等目标性活动中的自我约束和行为管理能力。它涉及到个体的责任感、自律性、计划性、条理性以及完成任务的态度。高分者往往表现出强烈的责任心、良好的组织能力、谨慎的决策风格和持续的努力精神;低分者则可能表现出随意性强、缺乏规划、做事马虎或易放弃的特点。",
+ "description": "反映个体在目标导向行为上的组织、坚持和动机特征。这个维度体现了个体在工作、"
+ "学习等目标性活动中的自我约束和行为管理能力。它涉及到个体的责任感、自律性、计划性、条理性以及完成任务的态度。"
+ "高分者往往表现出强烈的责任心、良好的组织能力、谨慎的决策风格和持续的努力精神;低分者则可能表现出随意性强、"
+ "缺乏规划、做事马虎或易放弃的特点。",
"trait_words": ["负责", "自律", "条理", "勤奋"],
"subfactors": {
- "责任心": "个体对待任务和他人认真负责,以及对自己承诺的信守;高分表现有责任心、负责任,低分表现推卸责任、逃避处罚",
+ "责任心": "个体对待任务和他人认真负责,以及对自己承诺的信守;高分表现有责任心、负责任,"
+ "低分表现推卸责任、逃避处罚",
"自我控制": "个体约束自己的能力,及自始至终的坚持性;高分表现自制、有毅力,低分表现冲动、无毅力",
"审慎性": "个体在采取具体行动前的心理状态;高分表现谨慎、小心,低分表现鲁莽、草率",
"条理性": "个体处理事务和工作的秩序,条理和逻辑性;高分表现整洁、有秩序,低分表现混乱、遗漏",
- "勤奋": "个体工作和学习的努力程度及为达到目标而表现出的进取精神;高分表现勤奋、刻苦,低分表现懒散"
- }
+ "勤奋": "个体工作和学习的努力程度及为达到目标而表现出的进取精神;高分表现勤奋、刻苦,低分表现懒散",
+ },
},
"开放性": {
- "description": "反映个体对新异事物、新观念和新经验的接受程度,以及在思维和行为方面的创新倾向。这个维度体现了个体在认知和体验方面的广度、深度和灵活性。它包括对艺术的欣赏能力、对知识的求知欲、想象力的丰富程度,以及对冒险和创新的态度。高分者往往具有丰富的想象力、广泛的兴趣、开放的思维方式和创新的倾向;低分者则倾向于保守、传统,喜欢熟悉和常规的事物。",
+ "description": "反映个体对新异事物、新观念和新经验的接受程度,以及在思维和行为方面的创新倾向。"
+ "这个维度体现了个体在认知和体验方面的广度、深度和灵活性。它包括对艺术的欣赏能力、对知识的求知欲、想象力的丰富程度,"
+ "以及对冒险和创新的态度。高分者往往具有丰富的想象力、广泛的兴趣、开放的思维方式和创新的倾向;低分者则倾向于保守、"
+ "传统,喜欢熟悉和常规的事物。",
"trait_words": ["创新", "好奇", "艺术", "冒险"],
"subfactors": {
"幻想": "个体富于幻想和想象的水平;高分表现想象力丰富,低分表现想象力匮乏",
"审美": "个体对于艺术和美的敏感与热爱程度;高分表现富有艺术气息,低分表现一般对艺术不敏感",
"好奇心": "个体对未知事物的态度;高分表现兴趣广泛、好奇心浓,低分表现兴趣少、无好奇心",
"冒险精神": "个体愿意尝试有风险活动的个体差异;高分表现好冒险,低分表现保守",
- "价值观念": "个体对新事物、新观念、怪异想法的态度;高分表现开放、坦然接受新事物,低分则相反"
- }
+ "价值观念": "个体对新事物、新观念、怪异想法的态度;高分表现开放、坦然接受新事物,低分则相反",
+ },
},
"宜人性": {
- "description": "反映个体在人际关系中的亲和倾向,体现了对他人的关心、同情和合作意愿。这个维度主要关注个体与他人互动时的态度和行为特征,包括对他人的信任程度、同理心水平、助人意愿以及在人际冲突中的处理方式。高分者通常表现出友善、富有同情心、乐于助人的特质,善于与他人建立和谐关系;低分者则可能表现出较少的人际关注,在社交互动中更注重自身利益,较少考虑他人感受。",
+ "description": "反映个体在人际关系中的亲和倾向,体现了对他人的关心、同情和合作意愿。"
+ "这个维度主要关注个体与他人互动时的态度和行为特征,包括对他人的信任程度、同理心水平、"
+ "助人意愿以及在人际冲突中的处理方式。高分者通常表现出友善、富有同情心、乐于助人的特质,善于与他人建立和谐关系;"
+ "低分者则可能表现出较少的人际关注,在社交互动中更注重自身利益,较少考虑他人感受。",
"trait_words": ["友善", "同理", "信任", "合作"],
"subfactors": {
"信任": "个体对他人和/或他人言论的相信程度;高分表现信任他人,低分表现怀疑",
"体贴": "个体对别人的兴趣和需要的关注程度;高分表现体贴、温存,低分表现冷漠、不在乎",
- "同情": "个体对处于不利地位的人或物的态度;高分表现富有同情心,低分表现冷漠"
- }
- }
-}
\ No newline at end of file
+ "同情": "个体对处于不利地位的人或物的态度;高分表现富有同情心,低分表现冷漠",
+ },
+ },
+}
diff --git a/src/plugins/personality/renqingziji.py b/src/plugins/personality/renqingziji.py
index b3a3e267e..4b1fb3b69 100644
--- a/src/plugins/personality/renqingziji.py
+++ b/src/plugins/personality/renqingziji.py
@@ -1,10 +1,12 @@
-'''
-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.'''
+"""
+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
@@ -13,9 +15,9 @@ 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"
@@ -23,9 +25,9 @@ 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,get_all_scenes,PERSONALITY_SCENES
-from src.plugins.personality.questionnaire import PERSONALITY_QUESTIONS,FACTOR_DESCRIPTIONS
-from src.plugins.personality.offline_llm import LLMModel
+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():
@@ -40,32 +42,31 @@ 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.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]:
@@ -78,9 +79,9 @@ class PersonalityEvaluator_direct:
desc = FACTOR_DESCRIPTIONS.get(dim, "")
if desc:
dimension_descriptions.append(f"- {dim}:{desc}")
-
+
dimensions_text = "\n".join(dimension_descriptions)
-
+
prompt = f"""请根据以下场景和用户描述,评估用户在大五人格模型中的相关维度得分(1-6分)。
场景描述:
@@ -178,11 +179,7 @@ def main():
print(f"测试场景数:{dimension_counts[trait]}")
# 保存结果
- result = {
- "final_scores": final_scores,
- "dimension_counts": dimension_counts,
- "scenarios": evaluator.scenarios
- }
+ result = {"final_scores": final_scores, "dimension_counts": dimension_counts, "scenarios": evaluator.scenarios}
# 确保目录存在
os.makedirs("results", exist_ok=True)
diff --git a/src/plugins/personality/scene.py b/src/plugins/personality/scene.py
index 936b07a3e..0ce094a36 100644
--- a/src/plugins/personality/scene.py
+++ b/src/plugins/personality/scene.py
@@ -1,4 +1,4 @@
-from typing import Dict, List
+from typing import Dict
PERSONALITY_SCENES = {
"外向性": {
@@ -8,7 +8,7 @@ PERSONALITY_SCENES = {
同事:「嗨!你是新来的同事吧?我是市场部的小林。」
同事看起来很友善,还主动介绍说:「待会午饭时间,我们部门有几个人准备一起去楼下新开的餐厅,你要一起来吗?可以认识一下其他同事。」""",
- "explanation": "这个场景通过职场社交情境,观察个体对于新环境、新社交圈的态度和反应倾向。"
+ "explanation": "这个场景通过职场社交情境,观察个体对于新环境、新社交圈的态度和反应倾向。",
},
"场景2": {
"scenario": """在大学班级群里,班长发起了一个组织班级联谊活动的投票:
@@ -16,7 +16,7 @@ PERSONALITY_SCENES = {
班长:「大家好!下周末我们准备举办一次班级联谊活动,地点在学校附近的KTV。想请大家报名参加,也欢迎大家邀请其他班级的同学!」
已经有几个同学在群里积极响应,有人@你问你要不要一起参加。""",
- "explanation": "通过班级活动场景,观察个体对群体社交活动的参与意愿。"
+ "explanation": "通过班级活动场景,观察个体对群体社交活动的参与意愿。",
},
"场景3": {
"scenario": """你在社交平台上发布了一条动态,收到了很多陌生网友的评论和私信:
@@ -24,13 +24,14 @@ PERSONALITY_SCENES = {
网友A:「你说的这个观点很有意思!想和你多交流一下。」
网友B:「我也对这个话题很感兴趣,要不要建个群一起讨论?」""",
- "explanation": "通过网络社交场景,观察个体对线上社交的态度。"
+ "explanation": "通过网络社交场景,观察个体对线上社交的态度。",
},
"场景4": {
"scenario": """你暗恋的对象今天主动来找你:
-对方:「那个...我最近在准备一个演讲比赛,听说你口才很好。能不能请你帮我看看演讲稿,顺便给我一些建议?如果你有时间的话,可以一起吃个饭聊聊。」""",
- "explanation": "通过恋爱情境,观察个体在面对心仪对象时的社交表现。"
+对方:「那个...我最近在准备一个演讲比赛,听说你口才很好。能不能请你帮我看看演讲稿,顺便给我一些建议?"""
+ """如果你有时间的话,可以一起吃个饭聊聊。」""",
+ "explanation": "通过恋爱情境,观察个体在面对心仪对象时的社交表现。",
},
"场景5": {
"scenario": """在一次线下读书会上,主持人突然点名让你分享读后感:
@@ -38,18 +39,18 @@ PERSONALITY_SCENES = {
主持人:「听说你对这本书很有见解,能不能和大家分享一下你的想法?」
现场有二十多个陌生的读书爱好者,都期待地看着你。""",
- "explanation": "通过即兴发言场景,观察个体的社交表现欲和公众表达能力。"
- }
+ "explanation": "通过即兴发言场景,观察个体的社交表现欲和公众表达能力。",
+ },
},
-
"神经质": {
"场景1": {
- "scenario": """你正在准备一个重要的项目演示,这关系到你的晋升机会。就在演示前30分钟,你收到了主管发来的消息:
+ "scenario": """你正在准备一个重要的项目演示,这关系到你的晋升机会。"""
+ """就在演示前30分钟,你收到了主管发来的消息:
主管:「临时有个变动,CEO也会来听你的演示。他对这个项目特别感兴趣。」
正当你准备回复时,主管又发来一条:「对了,能不能把演示时间压缩到15分钟?CEO下午还有其他安排。你之前准备的是30分钟的版本对吧?」""",
- "explanation": "这个场景通过突发的压力情境,观察个体在面对计划外变化时的情绪反应和调节能力。"
+ "explanation": "这个场景通过突发的压力情境,观察个体在面对计划外变化时的情绪反应和调节能力。",
},
"场景2": {
"scenario": """期末考试前一天晚上,你收到了好朋友发来的消息:
@@ -57,7 +58,7 @@ PERSONALITY_SCENES = {
好朋友:「不好意思这么晚打扰你...我看你平时成绩很好,能不能帮我解答几个问题?我真的很担心明天的考试。」
你看了看时间,已经是晚上11点,而你原本计划的复习还没完成。""",
- "explanation": "通过考试压力场景,观察个体在时间紧张时的情绪管理。"
+ "explanation": "通过考试压力场景,观察个体在时间紧张时的情绪管理。",
},
"场景3": {
"scenario": """你在社交媒体上发表的一个观点引发了争议,有不少人开始批评你:
@@ -67,7 +68,7 @@ PERSONALITY_SCENES = {
网友B:「建议楼主先去补补课再来发言。」
评论区里的负面评论越来越多,还有人开始人身攻击。""",
- "explanation": "通过网络争议场景,观察个体面对批评时的心理承受能力。"
+ "explanation": "通过网络争议场景,观察个体面对批评时的心理承受能力。",
},
"场景4": {
"scenario": """你和恋人约好今天一起看电影,但在约定时间前半小时,对方发来消息:
@@ -77,7 +78,7 @@ PERSONALITY_SCENES = {
二十分钟后,对方又发来消息:「可能要再等等,抱歉!」
电影快要开始了,但对方还是没有出现。""",
- "explanation": "通过恋爱情境,观察个体对不确定性的忍耐程度。"
+ "explanation": "通过恋爱情境,观察个体对不确定性的忍耐程度。",
},
"场景5": {
"scenario": """在一次重要的小组展示中,你的组员在演示途中突然卡壳了:
@@ -85,10 +86,9 @@ PERSONALITY_SCENES = {
组员小声对你说:「我忘词了,接下来的部分是什么来着...」
台下的老师和同学都在等待,气氛有些尴尬。""",
- "explanation": "通过公开场合的突发状况,观察个体的应急反应和压力处理能力。"
- }
+ "explanation": "通过公开场合的突发状况,观察个体的应急反应和压力处理能力。",
+ },
},
-
"严谨性": {
"场景1": {
"scenario": """你是团队的项目负责人,刚刚接手了一个为期两个月的重要项目。在第一次团队会议上:
@@ -98,7 +98,7 @@ PERSONALITY_SCENES = {
小张:「要不要先列个时间表?不过感觉太详细的计划也没必要,点到为止就行。」
小李:「客户那边说如果能提前完成有奖励,我觉得我们可以先做快一点的部分。」""",
- "explanation": "这个场景通过项目管理情境,体现个体在工作方法、计划性和责任心方面的特征。"
+ "explanation": "这个场景通过项目管理情境,体现个体在工作方法、计划性和责任心方面的特征。",
},
"场景2": {
"scenario": """期末小组作业,组长让大家分工完成一份研究报告。在截止日期前三天:
@@ -108,7 +108,7 @@ PERSONALITY_SCENES = {
组员B:「我这边可能还要一天才能完成,最近太忙了。」
组员C发来一份没有任何引用出处、可能存在抄袭的内容:「我写完了,你们看看怎么样?」""",
- "explanation": "通过学习场景,观察个体对学术规范和质量要求的重视程度。"
+ "explanation": "通过学习场景,观察个体对学术规范和质量要求的重视程度。",
},
"场景3": {
"scenario": """你在一个兴趣小组的群聊中,大家正在讨论举办一次线下活动:
@@ -118,7 +118,7 @@ PERSONALITY_SCENES = {
成员B:「对啊,随意一点挺好的。」
成员C:「人来了自然就热闹了。」""",
- "explanation": "通过活动组织场景,观察个体对活动计划的态度。"
+ "explanation": "通过活动组织场景,观察个体对活动计划的态度。",
},
"场景4": {
"scenario": """你和恋人计划一起去旅游,对方说:
@@ -126,7 +126,7 @@ PERSONALITY_SCENES = {
恋人:「我们就随心而行吧!订个目的地,其他的到了再说,这样更有意思。」
距离出发还有一周时间,但机票、住宿和具体行程都还没有确定。""",
- "explanation": "通过旅行规划场景,观察个体的计划性和对不确定性的接受程度。"
+ "explanation": "通过旅行规划场景,观察个体的计划性和对不确定性的接受程度。",
},
"场景5": {
"scenario": """在一个重要的团队项目中,你发现一个同事的工作存在明显错误:
@@ -134,18 +134,19 @@ PERSONALITY_SCENES = {
同事:「差不多就行了,反正领导也看不出来。」
这个错误可能不会立即造成问题,但长期来看可能会影响项目质量。""",
- "explanation": "通过工作质量场景,观察个体对细节和标准的坚持程度。"
- }
+ "explanation": "通过工作质量场景,观察个体对细节和标准的坚持程度。",
+ },
},
-
"开放性": {
"场景1": {
"scenario": """周末下午,你的好友小美兴致勃勃地给你打电话:
-小美:「我刚发现一个特别有意思的沉浸式艺术展!不是传统那种挂画的展览,而是把整个空间都变成了艺术品。观众要穿特制的服装,还要带上VR眼镜,好像还有AI实时互动!」
+小美:「我刚发现一个特别有意思的沉浸式艺术展!不是传统那种挂画的展览,而是把整个空间都变成了艺术品。"""
+ """观众要穿特制的服装,还要带上VR眼镜,好像还有AI实时互动!」
-小美继续说:「虽然票价不便宜,但听说体验很独特。网上评价两极分化,有人说是前所未有的艺术革新,也有人说是哗众取宠。要不要周末一起去体验一下?」""",
- "explanation": "这个场景通过新型艺术体验,反映个体对创新事物的接受程度和尝试意愿。"
+小美继续说:「虽然票价不便宜,但听说体验很独特。网上评价两极分化,有人说是前所未有的艺术革新,也有人说是哗众取宠。"""
+ """要不要周末一起去体验一下?」""",
+ "explanation": "这个场景通过新型艺术体验,反映个体对创新事物的接受程度和尝试意愿。",
},
"场景2": {
"scenario": """在一节创意写作课上,老师提出了一个特别的作业:
@@ -153,15 +154,16 @@ PERSONALITY_SCENES = {
老师:「下周的作业是用AI写作工具协助创作一篇小说。你们可以自由探索如何与AI合作,打破传统写作方式。」
班上随即展开了激烈讨论,有人认为这是对创作的亵渎,也有人对这种新形式感到兴奋。""",
- "explanation": "通过新技术应用场景,观察个体对创新学习方式的态度。"
+ "explanation": "通过新技术应用场景,观察个体对创新学习方式的态度。",
},
"场景3": {
"scenario": """在社交媒体上,你看到一个朋友分享了一种新的生活方式:
-「最近我在尝试'数字游牧'生活,就是一边远程工作一边环游世界。没有固定住所,住青旅或短租,认识来自世界各地的朋友。虽然有时会很不稳定,但这种自由的生活方式真的很棒!」
+「最近我在尝试'数字游牧'生活,就是一边远程工作一边环游世界。"""
+ """没有固定住所,住青旅或短租,认识来自世界各地的朋友。虽然有时会很不稳定,但这种自由的生活方式真的很棒!」
评论区里争论不断,有人向往这种生活,也有人觉得太冒险。""",
- "explanation": "通过另类生活方式,观察个体对非传统选择的态度。"
+ "explanation": "通过另类生活方式,观察个体对非传统选择的态度。",
},
"场景4": {
"scenario": """你的恋人突然提出了一个想法:
@@ -169,7 +171,7 @@ PERSONALITY_SCENES = {
恋人:「我们要不要尝试一下开放式关系?就是在保持彼此关系的同时,也允许和其他人发展感情。现在国外很多年轻人都这样。」
这个提议让你感到意外,你之前从未考虑过这种可能性。""",
- "explanation": "通过感情观念场景,观察个体对非传统关系模式的接受度。"
+ "explanation": "通过感情观念场景,观察个体对非传统关系模式的接受度。",
},
"场景5": {
"scenario": """在一次朋友聚会上,大家正在讨论未来职业规划:
@@ -179,10 +181,9 @@ PERSONALITY_SCENES = {
朋友B:「我想去学习生物科技,准备转行做人造肉研发。」
朋友C:「我在考虑加入一个区块链创业项目,虽然风险很大。」""",
- "explanation": "通过职业选择场景,观察个体对新兴领域的探索意愿。"
- }
+ "explanation": "通过职业选择场景,观察个体对新兴领域的探索意愿。",
+ },
},
-
"宜人性": {
"场景1": {
"scenario": """在回家的公交车上,你遇到这样一幕:
@@ -194,7 +195,7 @@ PERSONALITY_SCENES = {
年轻人B:「现在的老年人真是...我看她包里还有菜,肯定是去菜市场买完菜回来的,这么多人都不知道叫子女开车接送。」
就在这时,老奶奶一个趔趄,差点摔倒。她扶住了扶手,但包里的东西洒了一些出来。""",
- "explanation": "这个场景通过公共场合的助人情境,体现个体的同理心和对他人需求的关注程度。"
+ "explanation": "这个场景通过公共场合的助人情境,体现个体的同理心和对他人需求的关注程度。",
},
"场景2": {
"scenario": """在班级群里,有同学发起为生病住院的同学捐款:
@@ -204,7 +205,7 @@ PERSONALITY_SCENES = {
同学B:「我觉得这是他家里的事,我们不方便参与吧。」
同学C:「但是都是同学一场,帮帮忙也是应该的。」""",
- "explanation": "通过同学互助场景,观察个体的助人意愿和同理心。"
+ "explanation": "通过同学互助场景,观察个体的助人意愿和同理心。",
},
"场景3": {
"scenario": """在一个网络讨论组里,有人发布了求助信息:
@@ -215,7 +216,7 @@ PERSONALITY_SCENES = {
「生活本来就是这样,想开点!」
「你这样子太消极了,要积极面对。」
「谁还没点烦心事啊,过段时间就好了。」""",
- "explanation": "通过网络互助场景,观察个体的共情能力和安慰方式。"
+ "explanation": "通过网络互助场景,观察个体的共情能力和安慰方式。",
},
"场景4": {
"scenario": """你的恋人向你倾诉工作压力:
@@ -223,7 +224,7 @@ PERSONALITY_SCENES = {
恋人:「最近工作真的好累,感觉快坚持不下去了...」
但今天你也遇到了很多烦心事,心情也不太好。""",
- "explanation": "通过感情关系场景,观察个体在自身状态不佳时的关怀能力。"
+ "explanation": "通过感情关系场景,观察个体在自身状态不佳时的关怀能力。",
},
"场景5": {
"scenario": """在一次团队项目中,新来的同事小王因为经验不足,造成了一个严重的错误。在部门会议上:
@@ -231,27 +232,29 @@ PERSONALITY_SCENES = {
主管:「这个错误造成了很大的损失,是谁负责的这部分?」
小王看起来很紧张,欲言又止。你知道是他造成的错误,同时你也是这个项目的共同负责人。""",
- "explanation": "通过职场情境,观察个体在面对他人过错时的态度和处理方式。"
- }
- }
+ "explanation": "通过职场情境,观察个体在面对他人过错时的态度和处理方式。",
+ },
+ },
}
+
def get_scene_by_factor(factor: str) -> Dict:
"""
根据人格因子获取对应的情景测试
-
+
Args:
factor (str): 人格因子名称
-
+
Returns:
Dict: 包含情景描述的字典
"""
return PERSONALITY_SCENES.get(factor, None)
+
def get_all_scenes() -> Dict:
"""
获取所有情景测试
-
+
Returns:
Dict: 所有情景测试的字典
"""
diff --git a/webui.py b/webui.py
index b598df7c0..60ffa4805 100644
--- a/webui.py
+++ b/webui.py
@@ -4,11 +4,14 @@ import toml
import signal
import sys
import requests
+
try:
from src.common.logger import get_module_logger
+
logger = get_module_logger("webui")
except ImportError:
from loguru import logger
+
# 检查并创建日志目录
log_dir = "logs/webui"
if not os.path.exists(log_dir):
@@ -24,11 +27,13 @@ import ast
from packaging import version
from decimal import Decimal
+
def signal_handler(signum, frame):
"""处理 Ctrl+C 信号"""
logger.info("收到终止信号,正在关闭 Gradio 服务器...")
sys.exit(0)
+
# 注册信号处理器
signal.signal(signal.SIGINT, signal_handler)
@@ -44,10 +49,10 @@ if not os.path.exists(".env.prod"):
raise FileNotFoundError("环境配置文件 .env.prod 不存在,请检查配置文件路径")
config_data = toml.load("config/bot_config.toml")
-#增加对老版本配置文件支持
+# 增加对老版本配置文件支持
LEGACY_CONFIG_VERSION = version.parse("0.0.1")
-#增加最低支持版本
+# 增加最低支持版本
MIN_SUPPORT_VERSION = version.parse("0.0.8")
MIN_SUPPORT_MAIMAI_VERSION = version.parse("0.5.13")
@@ -66,7 +71,7 @@ else:
HAVE_ONLINE_STATUS_VERSION = version.parse("0.0.9")
-#定义意愿模式可选项
+# 定义意愿模式可选项
WILLING_MODE_CHOICES = [
"classical",
"dynamic",
@@ -74,11 +79,10 @@ WILLING_MODE_CHOICES = [
]
-
-
-#添加WebUI配置文件版本
+# 添加WebUI配置文件版本
WEBUI_VERSION = version.parse("0.0.9")
+
# ==============================================
# env环境配置文件读取部分
def parse_env_config(config_file):
@@ -204,7 +208,7 @@ MODEL_PROVIDER_LIST = parse_model_providers(env_config_data)
# env读取保存结束
# ==============================================
-#获取在线麦麦数量
+# 获取在线麦麦数量
def get_online_maimbot(url="http://hyybuth.xyz:10058/api/clients/details", timeout=10):
@@ -331,19 +335,19 @@ def format_list_to_str(lst):
# env保存函数
def save_trigger(
- server_address,
- server_port,
- final_result_list,
- t_mongodb_host,
- t_mongodb_port,
- t_mongodb_database_name,
- t_console_log_level,
- t_file_log_level,
- t_default_console_log_level,
- t_default_file_log_level,
- t_api_provider,
- t_api_base_url,
- t_api_key,
+ server_address,
+ server_port,
+ final_result_list,
+ t_mongodb_host,
+ t_mongodb_port,
+ t_mongodb_database_name,
+ t_console_log_level,
+ t_file_log_level,
+ t_default_console_log_level,
+ t_default_file_log_level,
+ t_api_provider,
+ t_api_base_url,
+ t_api_key,
):
final_result_lists = format_list_to_str(final_result_list)
env_config_data["env_HOST"] = server_address
@@ -412,12 +416,12 @@ def save_bot_config(t_qqbot_qq, t_nickname, t_nickname_final_result):
# 监听滑块的值变化,确保总和不超过 1,并显示警告
def adjust_personality_greater_probabilities(
- t_personality_1_probability, t_personality_2_probability, t_personality_3_probability
+ t_personality_1_probability, t_personality_2_probability, t_personality_3_probability
):
total = (
- Decimal(str(t_personality_1_probability))
- + Decimal(str(t_personality_2_probability))
- + Decimal(str(t_personality_3_probability))
+ Decimal(str(t_personality_1_probability))
+ + Decimal(str(t_personality_2_probability))
+ + Decimal(str(t_personality_3_probability))
)
if total > Decimal("1.0"):
warning_message = (
@@ -428,12 +432,12 @@ def adjust_personality_greater_probabilities(
def adjust_personality_less_probabilities(
- t_personality_1_probability, t_personality_2_probability, t_personality_3_probability
+ t_personality_1_probability, t_personality_2_probability, t_personality_3_probability
):
total = (
- Decimal(str(t_personality_1_probability))
- + Decimal(str(t_personality_2_probability))
- + Decimal(str(t_personality_3_probability))
+ Decimal(str(t_personality_1_probability))
+ + Decimal(str(t_personality_2_probability))
+ + Decimal(str(t_personality_3_probability))
)
if total < Decimal("1.0"):
warning_message = (
@@ -445,9 +449,7 @@ def adjust_personality_less_probabilities(
def adjust_model_greater_probabilities(t_model_1_probability, t_model_2_probability, t_model_3_probability):
total = (
- Decimal(str(t_model_1_probability)) +
- Decimal(str(t_model_2_probability)) +
- Decimal(str(t_model_3_probability))
+ Decimal(str(t_model_1_probability)) + Decimal(str(t_model_2_probability)) + Decimal(str(t_model_3_probability))
)
if total > Decimal("1.0"):
warning_message = (
@@ -459,9 +461,7 @@ def adjust_model_greater_probabilities(t_model_1_probability, t_model_2_probabil
def adjust_model_less_probabilities(t_model_1_probability, t_model_2_probability, t_model_3_probability):
total = (
- Decimal(str(t_model_1_probability))
- + Decimal(str(t_model_2_probability))
- + Decimal(str(t_model_3_probability))
+ Decimal(str(t_model_1_probability)) + Decimal(str(t_model_2_probability)) + Decimal(str(t_model_3_probability))
)
if total < Decimal("1.0"):
warning_message = (
@@ -474,13 +474,13 @@ def adjust_model_less_probabilities(t_model_1_probability, t_model_2_probability
# ==============================================
# 人格保存函数
def save_personality_config(
- t_prompt_personality_1,
- t_prompt_personality_2,
- t_prompt_personality_3,
- t_prompt_schedule,
- t_personality_1_probability,
- t_personality_2_probability,
- t_personality_3_probability,
+ t_prompt_personality_1,
+ t_prompt_personality_2,
+ t_prompt_personality_3,
+ t_prompt_schedule,
+ t_personality_1_probability,
+ t_personality_2_probability,
+ t_personality_3_probability,
):
# 保存人格提示词
config_data["personality"]["prompt_personality"][0] = t_prompt_personality_1
@@ -501,20 +501,20 @@ def save_personality_config(
def save_message_and_emoji_config(
- t_min_text_length,
- t_max_context_size,
- t_emoji_chance,
- t_thinking_timeout,
- t_response_willing_amplifier,
- t_response_interested_rate_amplifier,
- t_down_frequency_rate,
- t_ban_words_final_result,
- t_ban_msgs_regex_final_result,
- t_check_interval,
- t_register_interval,
- t_auto_save,
- t_enable_check,
- t_check_prompt,
+ t_min_text_length,
+ t_max_context_size,
+ t_emoji_chance,
+ t_thinking_timeout,
+ t_response_willing_amplifier,
+ t_response_interested_rate_amplifier,
+ t_down_frequency_rate,
+ t_ban_words_final_result,
+ t_ban_msgs_regex_final_result,
+ t_check_interval,
+ t_register_interval,
+ t_auto_save,
+ t_enable_check,
+ t_check_prompt,
):
config_data["message"]["min_text_length"] = t_min_text_length
config_data["message"]["max_context_size"] = t_max_context_size
@@ -536,27 +536,27 @@ def save_message_and_emoji_config(
def save_response_model_config(
- t_willing_mode,
- t_model_r1_probability,
- t_model_r2_probability,
- t_model_r3_probability,
- t_max_response_length,
- t_model1_name,
- t_model1_provider,
- t_model1_pri_in,
- t_model1_pri_out,
- t_model2_name,
- t_model2_provider,
- t_model3_name,
- t_model3_provider,
- t_emotion_model_name,
- t_emotion_model_provider,
- t_topic_judge_model_name,
- t_topic_judge_model_provider,
- t_summary_by_topic_model_name,
- t_summary_by_topic_model_provider,
- t_vlm_model_name,
- t_vlm_model_provider,
+ t_willing_mode,
+ t_model_r1_probability,
+ t_model_r2_probability,
+ t_model_r3_probability,
+ t_max_response_length,
+ t_model1_name,
+ t_model1_provider,
+ t_model1_pri_in,
+ t_model1_pri_out,
+ t_model2_name,
+ t_model2_provider,
+ t_model3_name,
+ t_model3_provider,
+ t_emotion_model_name,
+ t_emotion_model_provider,
+ t_topic_judge_model_name,
+ t_topic_judge_model_provider,
+ t_summary_by_topic_model_name,
+ t_summary_by_topic_model_provider,
+ t_vlm_model_name,
+ t_vlm_model_provider,
):
if PARSED_CONFIG_VERSION >= version.parse("0.0.10"):
config_data["willing"]["willing_mode"] = t_willing_mode
@@ -586,15 +586,15 @@ def save_response_model_config(
def save_memory_mood_config(
- t_build_memory_interval,
- t_memory_compress_rate,
- t_forget_memory_interval,
- t_memory_forget_time,
- t_memory_forget_percentage,
- t_memory_ban_words_final_result,
- t_mood_update_interval,
- t_mood_decay_rate,
- t_mood_intensity_factor,
+ t_build_memory_interval,
+ t_memory_compress_rate,
+ t_forget_memory_interval,
+ t_memory_forget_time,
+ t_memory_forget_percentage,
+ t_memory_ban_words_final_result,
+ t_mood_update_interval,
+ t_mood_decay_rate,
+ t_mood_intensity_factor,
):
config_data["memory"]["build_memory_interval"] = t_build_memory_interval
config_data["memory"]["memory_compress_rate"] = t_memory_compress_rate
@@ -611,17 +611,17 @@ def save_memory_mood_config(
def save_other_config(
- t_keywords_reaction_enabled,
- t_enable_advance_output,
- t_enable_kuuki_read,
- t_enable_debug_output,
- t_enable_friend_chat,
- t_chinese_typo_enabled,
- t_error_rate,
- t_min_freq,
- t_tone_error_rate,
- t_word_replace_rate,
- t_remote_status,
+ t_keywords_reaction_enabled,
+ t_enable_advance_output,
+ t_enable_kuuki_read,
+ t_enable_debug_output,
+ t_enable_friend_chat,
+ t_chinese_typo_enabled,
+ t_error_rate,
+ t_min_freq,
+ t_tone_error_rate,
+ t_word_replace_rate,
+ t_remote_status,
):
config_data["keywords_reaction"]["enable"] = t_keywords_reaction_enabled
config_data["others"]["enable_advance_output"] = t_enable_advance_output
@@ -641,9 +641,9 @@ def save_other_config(
def save_group_config(
- t_talk_allowed_final_result,
- t_talk_frequency_down_final_result,
- t_ban_user_id_final_result,
+ t_talk_allowed_final_result,
+ t_talk_frequency_down_final_result,
+ t_ban_user_id_final_result,
):
config_data["groups"]["talk_allowed"] = t_talk_allowed_final_result
config_data["groups"]["talk_frequency_down"] = t_talk_frequency_down_final_result
@@ -1212,10 +1212,10 @@ with gr.Blocks(title="MaimBot配置文件编辑") as app:
willing_mode = gr.Dropdown(
choices=WILLING_MODE_CHOICES,
value=config_data["willing"]["willing_mode"],
- label="回复意愿模式"
+ label="回复意愿模式",
)
else:
- willing_mode = gr.Textbox(visible=False,value="disabled")
+ willing_mode = gr.Textbox(visible=False, value="disabled")
with gr.Row():
model_r1_probability = gr.Slider(
minimum=0,