Merge branch 'main-fix' of https://github.com/MaiM-with-u/MaiBot into main-fix

This commit is contained in:
SengokuCola
2025-03-21 14:40:54 +08:00
27 changed files with 673 additions and 556 deletions

View File

@@ -101,7 +101,7 @@
<div align="center"> <div align="center">
<a href="https://www.bilibili.com/video/BV1amAneGE3P" target="_blank"> <a href="https://www.bilibili.com/video/BV1amAneGE3P" target="_blank">
<img src="docs/video.png" width="300" alt="麦麦演示视频"> <img src="docs/pic/video.png" width="300" alt="麦麦演示视频">
<br> <br>
👆 点击观看麦麦演示视频 👆 👆 点击观看麦麦演示视频 👆
@@ -149,6 +149,8 @@ MaiMBot是一个开源项目我们非常欢迎你的参与。你的贡献
- [📦 Linux 手动部署指南 ](docs/manual_deploy_linux.md) - [📦 Linux 手动部署指南 ](docs/manual_deploy_linux.md)
- [📦 macOS 手动部署指南 ](docs/manual_deploy_macos.md)
如果你不知道Docker是什么建议寻找相关教程或使用手动部署 **现在不建议使用docker更新慢可能不适配** 如果你不知道Docker是什么建议寻找相关教程或使用手动部署 **现在不建议使用docker更新慢可能不适配**
- [🐳 Docker部署指南](docs/docker_deploy.md) - [🐳 Docker部署指南](docs/docker_deploy.md)

14
bot.py
View File

@@ -204,8 +204,8 @@ def check_eula():
eula_confirmed = True eula_confirmed = True
eula_updated = False eula_updated = False
if eula_new_hash == os.getenv("EULA_AGREE"): if eula_new_hash == os.getenv("EULA_AGREE"):
eula_confirmed = True eula_confirmed = True
eula_updated = False eula_updated = False
# 检查隐私条款确认文件是否存在 # 检查隐私条款确认文件是否存在
if privacy_confirm_file.exists(): if privacy_confirm_file.exists():
@@ -214,14 +214,16 @@ def check_eula():
if privacy_new_hash == confirmed_content: if privacy_new_hash == confirmed_content:
privacy_confirmed = True privacy_confirmed = True
privacy_updated = False privacy_updated = False
if privacy_new_hash == os.getenv("PRIVACY_AGREE"): if privacy_new_hash == os.getenv("PRIVACY_AGREE"):
privacy_confirmed = True privacy_confirmed = True
privacy_updated = False privacy_updated = False
# 如果EULA或隐私条款有更新提示用户重新确认 # 如果EULA或隐私条款有更新提示用户重新确认
if eula_updated or privacy_updated: if eula_updated or privacy_updated:
print("EULA或隐私条款内容已更新请在阅读后重新确认继续运行视为同意更新后的以上两款协议") 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: while True:
user_input = input().strip().lower() user_input = input().strip().lower()
if user_input in ["同意", "confirmed"]: if user_input in ["同意", "confirmed"]:

View File

@@ -10,7 +10,7 @@
- 为什么显示:"缺失必要的API KEY" ❓ - 为什么显示:"缺失必要的API KEY" ❓
<img src="API_KEY.png" width=650> <img src="./pic/API_KEY.png" width=650>
>你需要在 [Silicon Flow Api](https://cloud.siliconflow.cn/account/ak) 网站上注册一个账号然后点击这个链接打开API KEY获取页面。 >你需要在 [Silicon Flow Api](https://cloud.siliconflow.cn/account/ak) 网站上注册一个账号然后点击这个链接打开API KEY获取页面。
> >
@@ -41,19 +41,19 @@
>打开你的MongoDB Compass软件你会在左上角看到这样的一个界面 >打开你的MongoDB Compass软件你会在左上角看到这样的一个界面
> >
><img src="MONGO_DB_0.png" width=250> ><img src="./pic/MONGO_DB_0.png" width=250>
> >
><br> ><br>
> >
>点击 "CONNECT" 之后,点击展开 MegBot 标签栏 >点击 "CONNECT" 之后,点击展开 MegBot 标签栏
> >
><img src="MONGO_DB_1.png" width=250> ><img src="./pic/MONGO_DB_1.png" width=250>
> >
><br> ><br>
> >
>点进 "emoji" 再点击 "DELETE" 删掉所有条目,如图所示 >点进 "emoji" 再点击 "DELETE" 删掉所有条目,如图所示
> >
><img src="MONGO_DB_2.png" width=450> ><img src="./pic/MONGO_DB_2.png" width=450>
> >
><br> ><br>
> >

View File

@@ -1,48 +1,51 @@
# 面向纯新手的Linux服务器麦麦部署指南 # 面向纯新手的Linux服务器麦麦部署指南
## 你得先有一个服务器
为了能使麦麦在你的电脑关机之后还能运行,你需要一台不间断开机的主机,也就是我们常说的服务器。 ## 事前准备
为了能使麦麦不间断的运行,你需要一台一直开着的主机。
### 如果你想购买服务器
华为云、阿里云、腾讯云等等都是在国内可以选择的选择。 华为云、阿里云、腾讯云等等都是在国内可以选择的选择。
你可以去租一台最低配置的就足敷需要了,按月租大概十几块钱就能租到了。 租一台最低配置的就足敷需要了,按月租大概十几块钱就能租到了。
我们假设你已经租好了一台Linux架构的云服务器。我用的是阿里云ubuntu24.04,其他的原理相似。 ### 如果你不想购买服务器
你可以准备一台可以一直开着的电脑/主机,只需要保证能够正常访问互联网即可
我们假设你已经有了一台Linux架构的服务器。举例使用的是Ubuntu24.04,其他的原理相似。
## 0.我们就从零开始吧 ## 0.我们就从零开始吧
### 网络问题 ### 网络问题
为访问github相关界面推荐去下一款加速器新手可以试试watttoolkit。 为访问Github相关界面推荐去下一款加速器新手可以试试[Watt Toolkit](https://gitee.com/rmbgame/SteamTools/releases/latest)
### 安装包下载 ### 安装包下载
#### MongoDB #### 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 ![](./pic/MongoDB_Ubuntu_guide.png)
如果不是就在这里自行选择对应版本
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 #### QQ可选/Napcat
*如果你使用Napcat的脚本安装可以忽略此步*
在这里选择对应版本。 访问https://github.com/NapNeko/NapCatQQ/releases/latest
在图中所示区域可以找到QQ的下载链接选择对应版本下载即可
https://github.com/NapNeko/NapCatQQ/releases/tag/v4.6.7 从这里下载可以保证你下载到的QQ版本兼容最新版Napcat
![](./pic/QQ_Download_guide_Linux.png)
对于ubuntu24.04 x86来说是这个 如果你不想使用Napcat的脚本安装还需参考[Napcat-Linux手动安装](https://www.napcat.wiki/guide/boot/Shell-Linux-SemiAuto)
https://dldir1.qq.com/qqfile/qq/QQNT/ee4bd910/linuxqq_3.2.16-32793_amd64.deb
#### 麦麦 #### 麦麦
https://github.com/SengokuCola/MaiMBot/archive/refs/tags/0.5.8-alpha.zip 先打开https://github.com/MaiM-with-u/MaiBot/releases
往下滑找到这个
下载这个官方压缩包。 ![下载指引](./pic/linux_beginner_downloadguide.png "")
下载箭头所指这个压缩包。
### 路径 ### 路径
@@ -53,10 +56,10 @@ https://github.com/SengokuCola/MaiMBot/archive/refs/tags/0.5.8-alpha.zip
``` ```
moi moi
└─ mai └─ mai
├─ linuxqq_3.2.16-32793_amd64.deb ├─ linuxqq_3.2.16-32793_amd64.deb # linuxqq安装包
├─ mongodb-org-server_8.0.5_amd64.deb ├─ mongodb-org-server_8.0.5_amd64.deb # MongoDB的安装包
└─ bot └─ bot
└─ MaiMBot-0.5.8-alpha.zip └─ MaiMBot-0.5.8-alpha.zip # 麦麦的压缩包
``` ```
### 网络 ### 网络
@@ -69,7 +72,7 @@ moi
## 2. Python的安装 ## 2. Python的安装
- 导入 Python 的稳定版 PPA - 导入 Python 的稳定版 PPAUbuntu需执行此步Debian可忽略
```bash ```bash
sudo add-apt-repository ppa:deadsnakes/ppa sudo add-apt-repository ppa:deadsnakes/ppa
@@ -92,6 +95,11 @@ sudo apt install python3.12
```bash ```bash
python3.12 --version 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 - 在「终端」中,执行以下命令安装 pip
@@ -141,23 +149,17 @@ systemctl status mongod #通过这条指令检查运行状态
sudo systemctl enable mongod sudo systemctl enable mongod
``` ```
## 5.napcat的安装 ## 5.Napcat的安装
``` bash ``` 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 curl -o napcat.sh https://nclatest.znin.net/NapNeko/NapCat-Installer/main/script/install.sh && sudo bash napcat.sh
``` ```
执行后脚本会自动帮你部署好QQ及Napcat
上面的不行试试下面的
``` bash
dpkg -i linuxqq_3.2.16-32793_amd64.deb
apt-get install -f
dpkg -i linuxqq_3.2.16-32793_amd64.deb
```
成功的标志是输入``` napcat ```出来炫酷的彩虹色界面 成功的标志是输入``` napcat ```出来炫酷的彩虹色界面
## 6.napcat的运行 ## 6.Napcat的运行
此时你就可以根据提示在```napcat```里面登录你的QQ号了。 此时你就可以根据提示在```napcat```里面登录你的QQ号了。
@@ -170,6 +172,13 @@ napcat status #检查运行状态
```http://<你服务器的公网IP>:6099/webui?token=napcat``` ```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>即可。 第一次是这个后续改了密码之后token就会对应修改。你也可以使用```napcat log <你的QQ号>```来查看webui地址。把里面的```127.0.0.1```改成<你服务器的公网IP>即可。
登录上之后在网络配置界面添加websocket客户端名称随便输一个url改成`ws://127.0.0.1:8080/onebot/v11/ws`保存之后点启用,就大功告成了。 登录上之后在网络配置界面添加websocket客户端名称随便输一个url改成`ws://127.0.0.1:8080/onebot/v11/ws`保存之后点启用,就大功告成了。
@@ -178,7 +187,7 @@ napcat status #检查运行状态
### step 1 安装解压软件 ### step 1 安装解压软件
``` ```bash
sudo apt-get install unzip sudo apt-get install unzip
``` ```
@@ -229,138 +238,11 @@ bot
你可以注册一个硅基流动的账号通过邀请码注册有14块钱的免费额度https://cloud.siliconflow.cn/i/7Yld7cfg。 你可以注册一个硅基流动的账号通过邀请码注册有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** 运行 **step # 6** 运行
@@ -438,7 +320,7 @@ sudo systemctl enable bot.service # 启动bot服务
sudo systemctl status bot.service # 检查bot服务状态 sudo systemctl status bot.service # 检查bot服务状态
``` ```
``` ```bash
python bot.py python bot.py # 运行麦麦
``` ```

View File

@@ -6,7 +6,7 @@
- QQ小号QQ框架的使用可能导致qq被风控严重小概率可能会导致账号封禁强烈不推荐使用大号 - QQ小号QQ框架的使用可能导致qq被风控严重小概率可能会导致账号封禁强烈不推荐使用大号
- 可用的大模型API - 可用的大模型API
- 一个AI助手网上随便搜一家打开来用都行可以帮你解决一些不懂的问题 - 一个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版本** ### 1⃣ **确认Python版本**
@@ -36,17 +39,26 @@ python --version
python3 --version python3 --version
``` ```
如果版本低于3.9请更新Python版本 如果版本低于3.9请更新Python版本目前建议使用python3.12
```bash ```bash
# Ubuntu/Debian # Debian
sudo apt update sudo apt update
sudo apt install python3.9 sudo apt install python3.12
# 如执行了这一步建议在执行时将python3指向python3.9 # Ubuntu
# 更新替代方案,设置 python3.9 为默认的 python3 版本: sudo add-apt-repository ppa:deadsnakes/ppa
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 1 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 sudo update-alternatives --config python3
``` ```
建议再执行以下命令,使后续运行命令中的`python3`等同于`python`
```bash
sudo apt install python-is-python3
```
### 2⃣ **创建虚拟环境** ### 2⃣ **创建虚拟环境**
@@ -73,7 +85,7 @@ pip install -r requirements.txt
### 3⃣ **安装并启动MongoDB** ### 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端口 - 默认连接本地27017端口
--- ---
@@ -82,7 +94,11 @@ pip install -r requirements.txt
### 4⃣ **安装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)安装 - 执行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` - 使用QQ小号登录添加反向WS地址: `ws://127.0.0.1:8080/onebot/v11/ws`
@@ -91,9 +107,17 @@ pip install -r requirements.txt
## 配置文件设置 ## 配置文件设置
### 5⃣ **配置文件设置让麦麦Bot正常工作** ### 5⃣ **配置文件设置让麦麦Bot正常工作**
可先运行一次
- 修改环境配置文件:`.env.prod` ```bash
- 修改机器人配置文件:`bot_config.toml` # 在项目目录下操作
nb run
# 或
python3 bot.py
```
之后你就可以找到`.env.prod``bot_config.toml`这两个文件了
关于文件内容的配置请参考:
- [🎀 新手配置指南](./installation_cute.md) - 通俗易懂的配置教程,适合初次使用的猫娘
- [⚙️ 标准配置指南](./installation_standard.md) - 简明专业的配置说明,适合有经验的用户
--- ---

201
docs/manual_deploy_macos.md Normal file
View File

@@ -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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.maimbot</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/maimbot-venv/bin/python</string>
<string>/path/to/MaiMbot/bot.py</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/MaiMbot</string>
<key>StandardOutPath</key>
<string>/tmp/maimbot.log</string>
<key>StandardErrorPath</key>
<string>/tmp/maimbot.err</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
```
加载服务:
```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
```
---

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -16,7 +16,7 @@
docker-compose.yml: https://github.com/SengokuCola/MaiMBot/blob/main/docker-compose.yml docker-compose.yml: https://github.com/SengokuCola/MaiMBot/blob/main/docker-compose.yml
下载后打开,将 `services-mongodb-image` 修改为 `mongo:4.4.24`。这是因为最新的 MongoDB 强制要求 AVX 指令集,而群晖似乎不支持这个指令集 下载后打开,将 `services-mongodb-image` 修改为 `mongo:4.4.24`。这是因为最新的 MongoDB 强制要求 AVX 指令集,而群晖似乎不支持这个指令集
![](https://raw.githubusercontent.com/ProperSAMA/MaiMBot/refs/heads/debug/docs/synology_docker-compose.png) ![](./pic/synology_docker-compose.png)
bot_config.toml: https://github.com/SengokuCola/MaiMBot/blob/main/template/bot_config_template.toml bot_config.toml: https://github.com/SengokuCola/MaiMBot/blob/main/template/bot_config_template.toml
下载后,重命名为 `bot_config.toml` 下载后,重命名为 `bot_config.toml`
@@ -26,13 +26,13 @@ bot_config.toml: https://github.com/SengokuCola/MaiMBot/blob/main/template/bot_c
下载后,重命名为 `.env.prod` 下载后,重命名为 `.env.prod`
`HOST` 修改为 `0.0.0.0`,确保 maimbot 能被 napcat 访问 `HOST` 修改为 `0.0.0.0`,确保 maimbot 能被 napcat 访问
按下图修改 mongodb 设置,使用 `MONGODB_URI` 按下图修改 mongodb 设置,使用 `MONGODB_URI`
![](https://raw.githubusercontent.com/ProperSAMA/MaiMBot/refs/heads/debug/docs/synology_.env.prod.png) ![](./pic/synology_.env.prod.png)
`bot_config.toml``.env.prod` 放入之前创建的 `MaiMBot`文件夹 `bot_config.toml``.env.prod` 放入之前创建的 `MaiMBot`文件夹
#### 如何下载? #### 如何下载?
点这里!![](https://raw.githubusercontent.com/ProperSAMA/MaiMBot/refs/heads/debug/docs/synology_how_to_download.png) 点这里!![](./pic/synology_how_to_download.png)
### 创建项目 ### 创建项目
@@ -45,7 +45,7 @@ bot_config.toml: https://github.com/SengokuCola/MaiMBot/blob/main/template/bot_c
图例: 图例:
![](https://raw.githubusercontent.com/ProperSAMA/MaiMBot/refs/heads/debug/docs/synology_create_project.png) ![](./pic/synology_create_project.png)
一路点下一步,等待项目创建完成 一路点下一步,等待项目创建完成

View File

@@ -92,12 +92,13 @@ async def _(bot: Bot):
@msg_in.handle() @msg_in.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State): async def _(bot: Bot, event: MessageEvent, state: T_State):
#处理合并转发消息 # 处理合并转发消息
if "forward" in event.message: if "forward" in event.message:
await chat_bot.handle_forward_message(event , bot) await chat_bot.handle_forward_message(event, bot)
else : else:
await chat_bot.handle_message(event, bot) await chat_bot.handle_message(event, bot)
@notice_matcher.handle() @notice_matcher.handle()
async def _(bot: Bot, event: NoticeEvent, state: T_State): async def _(bot: Bot, event: NoticeEvent, state: T_State):
logger.debug(f"收到通知:{event}") logger.debug(f"收到通知:{event}")

View File

@@ -424,7 +424,6 @@ class ChatBot:
if event.group_id not in global_config.talk_allowed_groups: if event.group_id not in global_config.talk_allowed_groups:
return return
# 获取合并转发消息的详细信息 # 获取合并转发消息的详细信息
forward_info = await bot.get_forward_msg(message_id=event.message_id) forward_info = await bot.get_forward_msg(message_id=event.message_id)
messages = forward_info["messages"] messages = forward_info["messages"]
@@ -436,7 +435,7 @@ class ChatBot:
nickname = node["sender"].get("nickname", "未知用户") 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}") processed_messages.append(f"{nickname}{message_content}")
@@ -456,11 +455,7 @@ class ChatBot:
# 构建群聊信息(如果是群聊) # 构建群聊信息(如果是群聊)
group_info = None group_info = None
if isinstance(event, GroupMessageEvent): if isinstance(event, GroupMessageEvent):
group_info = GroupInfo( group_info = GroupInfo(group_id=event.group_id, group_name=None, platform="qq")
group_id=event.group_id,
group_name=None,
platform="qq"
)
# 创建消息对象 # 创建消息对象
message_cq = MessageRecvCQ( message_cq = MessageRecvCQ(
@@ -475,19 +470,19 @@ class ChatBot:
# 进入标准消息处理流程 # 进入标准消息处理流程
await self.message_process(message_cq) 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 = [] parts = []
for seg in segments: for seg in segments:
part = await self.process_segment(seg,layer+1) part = await self.process_segment(seg, layer + 1)
parts.append(part) parts.append(part)
return "".join(parts) 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"] seg_type = seg["type"]
if layer > 3 : if layer > 3:
#防止有那种100层转发消息炸飞麦麦 # 防止有那种100层转发消息炸飞麦麦
return "【转发消息】" return "【转发消息】"
if seg_type == "text": if seg_type == "text":
return seg["data"]["text"] return seg["data"]["text"]
@@ -504,7 +499,7 @@ class ChatBot:
nested_messages.append("合并转发消息内容:") nested_messages.append("合并转发消息内容:")
for node in nested_nodes: for node in nested_nodes:
nickname = node["sender"].get("nickname", "未知用户") 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('-' * layer)
nested_messages.append(f"{'--' * layer}{nickname}{content}") nested_messages.append(f"{'--' * layer}{nickname}{content}")
# nested_messages.append(f"{'--' * layer}合并转发第【{layer}】层结束") # nested_messages.append(f"{'--' * layer}合并转发第【{layer}】层结束")
@@ -512,5 +507,6 @@ class ChatBot:
else: else:
return f"[{seg_type}]" return f"[{seg_type}]"
# 创建全局ChatBot实例 # 创建全局ChatBot实例
chat_bot = ChatBot() chat_bot = ChatBot()

View File

@@ -15,10 +15,7 @@ env_path = project_root / ".env.prod"
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
sys.path.append(root_path) 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 # noqa: E402
from src.plugins.personality.questionnaire import PERSONALITY_QUESTIONS,FACTOR_DESCRIPTIONS
from src.plugins.personality.offline_llm import LLMModel
class BigFiveTest: class BigFiveTest:
@@ -39,7 +36,7 @@ class BigFiveTest:
print("\n请认真阅读每个描述,选择最符合您实际情况的选项。\n") print("\n请认真阅读每个描述,选择最符合您实际情况的选项。\n")
# 创建题目序号到题目的映射 # 创建题目序号到题目的映射
questions_map = {q['id']: q for q in self.questions} questions_map = {q["id"]: q for q in self.questions}
# 获取所有题目ID并随机打乱顺序 # 获取所有题目ID并随机打乱顺序
question_ids = list(questions_map.keys()) question_ids = list(questions_map.keys())
@@ -67,35 +64,25 @@ class BigFiveTest:
def calculate_scores(self, answers): def calculate_scores(self, answers):
"""计算各维度得分""" """计算各维度得分"""
results = {} results = {}
factor_questions = { factor_questions = {"外向性": [], "神经质": [], "严谨性": [], "开放性": [], "宜人性": []}
"外向性": [],
"神经质": [],
"严谨性": [],
"开放性": [],
"宜人性": []
}
# 将题目按因子分类 # 将题目按因子分类
for q in self.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(): for factor, questions in factor_questions.items():
total_score = 0 total_score = 0
for q in questions: 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减原始分 score = 7 - score # 6分量表反向计分为7减原始分
total_score += score total_score += score
# 计算平均分 # 计算平均分
avg_score = round(total_score / len(questions), 2) avg_score = round(total_score / len(questions), 2)
results[factor] = { results[factor] = {"得分": avg_score, "题目数": len(questions), "总分": total_score}
"得分": avg_score,
"题目数": len(questions),
"总分": total_score
}
return results return results
@@ -103,6 +90,7 @@ class BigFiveTest:
"""获取因子的详细描述""" """获取因子的详细描述"""
return self.factors[factor] return self.factors[factor]
def main(): def main():
test = BigFiveTest() test = BigFiveTest()
results = test.run_test() results = test.run_test()
@@ -114,9 +102,10 @@ def main():
print(f"平均分: {data['得分']} (总分: {data['总分']}, 题目数: {data['题目数']})") print(f"平均分: {data['得分']} (总分: {data['总分']}, 题目数: {data['题目数']})")
print("-" * 30) print("-" * 30)
description = test.get_factor_description(factor) description = test.get_factor_description(factor)
print("维度说明:", description['description'][:100] + "...") print("维度说明:", description["description"][:100] + "...")
print("\n特征词:", ", ".join(description['trait_words'])) print("\n特征词:", ", ".join(description["trait_words"]))
print("=" * 50) print("=" * 50)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1,4 +1,4 @@
from typing import Dict, List from typing import Dict
import json import json
import os import os
from pathlib import Path from pathlib import Path
@@ -14,9 +14,10 @@ env_path = project_root / ".env.prod"
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
sys.path.append(root_path) sys.path.append(root_path)
from src.plugins.personality.big5_test import BigFiveTest from src.plugins.personality.big5_test import BigFiveTest # noqa: E402
from src.plugins.personality.renqingziji import PersonalityEvaluator_direct from src.plugins.personality.renqingziji import PersonalityEvaluator_direct # noqa: E402
from src.plugins.personality.questionnaire import FACTOR_DESCRIPTIONS, PERSONALITY_QUESTIONS from src.plugins.personality.questionnaire import FACTOR_DESCRIPTIONS, PERSONALITY_QUESTIONS # noqa: E402
class CombinedPersonalityTest: class CombinedPersonalityTest:
def __init__(self): def __init__(self):
@@ -51,10 +52,7 @@ class CombinedPersonalityTest:
questionnaire_results = self.run_questionnaire() questionnaire_results = self.run_questionnaire()
# 转换问卷结果格式以便比较 # 转换问卷结果格式以便比较
questionnaire_scores = { questionnaire_scores = {factor: data["得分"] for factor, data in questionnaire_results.items()}
factor: data["得分"]
for factor, data in questionnaire_results.items()
}
# 运行情景测试 # 运行情景测试
print("\n=== 第二部分:情景反应测评 ===") print("\n=== 第二部分:情景反应测评 ===")
@@ -74,7 +72,7 @@ class CombinedPersonalityTest:
def run_questionnaire(self): 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并随机打乱顺序 # 获取所有题目ID并随机打乱顺序
question_ids = list(questions_map.keys()) question_ids = list(questions_map.keys())
@@ -100,42 +98,32 @@ class CombinedPersonalityTest:
# 每10题显示一次进度 # 每10题显示一次进度
if i % 10 == 0: 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) return self.calculate_questionnaire_scores(answers)
def calculate_questionnaire_scores(self, answers): def calculate_questionnaire_scores(self, answers):
"""计算问卷测试的维度得分""" """计算问卷测试的维度得分"""
results = {} results = {}
factor_questions = { factor_questions = {"外向性": [], "神经质": [], "严谨性": [], "开放性": [], "宜人性": []}
"外向性": [],
"神经质": [],
"严谨性": [],
"开放性": [],
"宜人性": []
}
# 将题目按因子分类 # 将题目按因子分类
for q in PERSONALITY_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(): for factor, questions in factor_questions.items():
total_score = 0 total_score = 0
for q in questions: 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减原始分 score = 7 - score # 6分量表反向计分为7减原始分
total_score += score total_score += score
# 计算平均分 # 计算平均分
avg_score = round(total_score / len(questions), 2) avg_score = round(total_score / len(questions), 2)
results[factor] = { results[factor] = {"得分": avg_score, "题目数": len(questions), "总分": total_score}
"得分": avg_score,
"题目数": len(questions),
"总分": total_score
}
return results return results
@@ -160,11 +148,7 @@ class CombinedPersonalityTest:
continue continue
print("\n正在评估您的描述...") print("\n正在评估您的描述...")
scores = self.scenario_test.evaluate_response( scores = self.scenario_test.evaluate_response(scenario_data["场景"], response, scenario_data["评估维度"])
scenario_data["场景"],
response,
scenario_data["评估维度"]
)
# 更新分数 # 更新分数
for dimension, score in scores.items(): for dimension, score in scores.items():
@@ -178,7 +162,7 @@ class CombinedPersonalityTest:
# 每5个场景显示一次总进度 # 每5个场景显示一次总进度
if i % 5 == 0: 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): if i < len(scenarios):
input("\n按回车继续下一个场景...") input("\n按回车继续下一个场景...")
@@ -186,10 +170,7 @@ class CombinedPersonalityTest:
# 计算平均分 # 计算平均分
for dimension in final_scores: for dimension in final_scores:
if dimension_counts[dimension] > 0: if dimension_counts[dimension] > 0:
final_scores[dimension] = round( final_scores[dimension] = round(final_scores[dimension] / dimension_counts[dimension], 2)
final_scores[dimension] / dimension_counts[dimension],
2
)
return final_scores return final_scores
@@ -225,9 +206,13 @@ class CombinedPersonalityTest:
std_diff = (sum((x - mean_diff) ** 2 for x in diffs) / (len(diffs) - 1)) ** 0.5 std_diff = (sum((x - mean_diff) ** 2 for x in diffs) / (len(diffs) - 1)) ** 0.5
# 计算效应量 (Cohen's d) # 计算效应量 (Cohen's d)
pooled_std = ((sum((x - sum(questionnaire_values)/len(questionnaire_values))**2 for x in questionnaire_values) + pooled_std = (
sum((x - sum(scenario_values)/len(scenario_values))**2 for x in scenario_values)) / (
(2 * len(self.dimensions) - 2)) ** 0.5 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: if pooled_std != 0:
cohens_d = abs(mean_diff / pooled_std) cohens_d = abs(mean_diff / pooled_std)
@@ -244,7 +229,7 @@ class CombinedPersonalityTest:
# 对所有维度进行整体t检验 # 对所有维度进行整体t检验
t_stat, p_value = stats.ttest_rel(questionnaire_values, scenario_values) t_stat, p_value = stats.ttest_rel(questionnaire_values, scenario_values)
print(f"\n整体统计分析:") print("\n整体统计分析:")
print(f"平均差异: {mean_diff:.3f}") print(f"平均差异: {mean_diff:.3f}")
print(f"差异标准差: {std_diff:.3f}") print(f"差异标准差: {std_diff:.3f}")
print(f"效应量(Cohen's d): {cohens_d:.3f}") print(f"效应量(Cohen's d): {cohens_d:.3f}")
@@ -269,12 +254,14 @@ class CombinedPersonalityTest:
for dimension in self.dimensions: for dimension in self.dimensions:
diff = abs(questionnaire_scores[dimension] - scenario_scores[dimension]) diff = abs(questionnaire_scores[dimension] - scenario_scores[dimension])
if diff >= 1.0: # 差异大于等于1分视为显著 if diff >= 1.0: # 差异大于等于1分视为显著
significant_diffs.append({ significant_diffs.append(
"dimension": dimension, {
"diff": diff, "dimension": dimension,
"questionnaire": questionnaire_scores[dimension], "diff": diff,
"scenario": scenario_scores[dimension] "questionnaire": questionnaire_scores[dimension],
}) "scenario": scenario_scores[dimension],
}
)
if significant_diffs: if significant_diffs:
print("\n\n显著差异分析:") print("\n\n显著差异分析:")
@@ -286,7 +273,7 @@ class CombinedPersonalityTest:
print(f"差异值:{diff['diff']:.2f}") print(f"差异值:{diff['diff']:.2f}")
# 分析可能的原因 # 分析可能的原因
if diff['questionnaire'] > diff['scenario']: if diff["questionnaire"] > diff["scenario"]:
print("可能原因:在问卷中的自我评价较高,但在具体情景中的表现较为保守。") print("可能原因:在问卷中的自我评价较高,但在具体情景中的表现较为保守。")
else: else:
print("可能原因:在具体情景中表现出更多该维度特征,而在问卷自评时较为保守。") print("可能原因:在具体情景中表现出更多该维度特征,而在问卷自评时较为保守。")
@@ -297,7 +284,7 @@ class CombinedPersonalityTest:
"测试时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "测试时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"问卷测评结果": questionnaire_scores, "问卷测评结果": questionnaire_scores,
"情景测评结果": scenario_scores, "情景测评结果": scenario_scores,
"维度说明": FACTOR_DESCRIPTIONS "维度说明": FACTOR_DESCRIPTIONS,
} }
# 确保目录存在 # 确保目录存在
@@ -312,6 +299,7 @@ class CombinedPersonalityTest:
print(f"\n完整的测评结果已保存到:{filename}") print(f"\n完整的测评结果已保存到:{filename}")
def load_existing_results(): def load_existing_results():
"""检查并加载已有的测试结果""" """检查并加载已有的测试结果"""
results_dir = "results" results_dir = "results"
@@ -319,15 +307,13 @@ def load_existing_results():
return None return None
# 获取所有personality_combined开头的文件 # 获取所有personality_combined开头的文件
result_files = [f for f in os.listdir(results_dir) result_files = [f for f in os.listdir(results_dir) if f.startswith("personality_combined_") and f.endswith(".json")]
if f.startswith("personality_combined_") and f.endswith(".json")]
if not result_files: if not result_files:
return None return None
# 按文件修改时间排序,获取最新的结果文件 # 按文件修改时间排序,获取最新的结果文件
latest_file = max(result_files, latest_file = max(result_files, key=lambda f: os.path.getmtime(os.path.join(results_dir, f)))
key=lambda f: os.path.getmtime(os.path.join(results_dir, f)))
print(f"\n发现已有的测试结果:{latest_file}") print(f"\n发现已有的测试结果:{latest_file}")
try: try:
@@ -338,6 +324,7 @@ def load_existing_results():
print(f"读取结果文件时出错:{str(e)}") print(f"读取结果文件时出错:{str(e)}")
return None return None
def main(): def main():
test = CombinedPersonalityTest() test = CombinedPersonalityTest()
@@ -357,5 +344,6 @@ def main():
print("\n未找到已有的测试结果,开始新的测试...") print("\n未找到已有的测试结果,开始新的测试...")
test.run_combined_test() test.run_combined_test()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -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 = [ PERSONALITY_QUESTIONS = [
# 神经质维度 (F1) # 神经质维度 (F1)
@@ -11,7 +15,6 @@ PERSONALITY_QUESTIONS = [
{"id": 6, "content": "在面对压力时,我有种快要崩溃的感觉", "factor": "神经质", "reverse_scoring": False}, {"id": 6, "content": "在面对压力时,我有种快要崩溃的感觉", "factor": "神经质", "reverse_scoring": False},
{"id": 7, "content": "我常担忧一些无关紧要的事情", "factor": "神经质", "reverse_scoring": False}, {"id": 7, "content": "我常担忧一些无关紧要的事情", "factor": "神经质", "reverse_scoring": False},
{"id": 8, "content": "我常常感到内心不踏实", "factor": "神经质", "reverse_scoring": False}, {"id": 8, "content": "我常常感到内心不踏实", "factor": "神经质", "reverse_scoring": False},
# 严谨性维度 (F2) # 严谨性维度 (F2)
{"id": 9, "content": "在工作上,我常只求能应付过去便可", "factor": "严谨性", "reverse_scoring": True}, {"id": 9, "content": "在工作上,我常只求能应付过去便可", "factor": "严谨性", "reverse_scoring": True},
{"id": 10, "content": "一旦确定了目标,我会坚持努力地实现它", "factor": "严谨性", "reverse_scoring": False}, {"id": 10, "content": "一旦确定了目标,我会坚持努力地实现它", "factor": "严谨性", "reverse_scoring": False},
@@ -21,9 +24,13 @@ PERSONALITY_QUESTIONS = [
{"id": 14, "content": "我喜欢一开头就把事情计划好", "factor": "严谨性", "reverse_scoring": False}, {"id": 14, "content": "我喜欢一开头就把事情计划好", "factor": "严谨性", "reverse_scoring": False},
{"id": 15, "content": "我工作或学习很勤奋", "factor": "严谨性", "reverse_scoring": False}, {"id": 15, "content": "我工作或学习很勤奋", "factor": "严谨性", "reverse_scoring": False},
{"id": 16, "content": "我是个倾尽全力做事的人", "factor": "严谨性", "reverse_scoring": False}, {"id": 16, "content": "我是个倾尽全力做事的人", "factor": "严谨性", "reverse_scoring": False},
# 宜人性维度 (F3) # 宜人性维度 (F3)
{"id": 17, "content": "尽管人类社会存在着一些阴暗的东西(如战争、罪恶、欺诈),我仍然相信人性总的来说是善良的", "factor": "宜人性", "reverse_scoring": False}, {
"id": 17,
"content": "尽管人类社会存在着一些阴暗的东西(如战争、罪恶、欺诈),我仍然相信人性总的来说是善良的",
"factor": "宜人性",
"reverse_scoring": False,
},
{"id": 18, "content": "我觉得大部分人基本上是心怀善意的", "factor": "宜人性", "reverse_scoring": False}, {"id": 18, "content": "我觉得大部分人基本上是心怀善意的", "factor": "宜人性", "reverse_scoring": False},
{"id": 19, "content": "虽然社会上有骗子,但我觉得大部分人还是可信的", "factor": "宜人性", "reverse_scoring": False}, {"id": 19, "content": "虽然社会上有骗子,但我觉得大部分人还是可信的", "factor": "宜人性", "reverse_scoring": False},
{"id": 20, "content": "我不太关心别人是否受到不公正的待遇", "factor": "宜人性", "reverse_scoring": True}, {"id": 20, "content": "我不太关心别人是否受到不公正的待遇", "factor": "宜人性", "reverse_scoring": True},
@@ -31,7 +38,6 @@ PERSONALITY_QUESTIONS = [
{"id": 22, "content": "我常为那些遭遇不幸的人感到难过", "factor": "宜人性", "reverse_scoring": False}, {"id": 22, "content": "我常为那些遭遇不幸的人感到难过", "factor": "宜人性", "reverse_scoring": False},
{"id": 23, "content": "我是那种只照顾好自己,不替别人担忧的人", "factor": "宜人性", "reverse_scoring": True}, {"id": 23, "content": "我是那种只照顾好自己,不替别人担忧的人", "factor": "宜人性", "reverse_scoring": True},
{"id": 24, "content": "当别人向我诉说不幸时,我常感到难过", "factor": "宜人性", "reverse_scoring": False}, {"id": 24, "content": "当别人向我诉说不幸时,我常感到难过", "factor": "宜人性", "reverse_scoring": False},
# 开放性维度 (F4) # 开放性维度 (F4)
{"id": 25, "content": "我的想象力相当丰富", "factor": "开放性", "reverse_scoring": False}, {"id": 25, "content": "我的想象力相当丰富", "factor": "开放性", "reverse_scoring": False},
{"id": 26, "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": 28, "content": "我喜欢冒险", "factor": "开放性", "reverse_scoring": False},
{"id": 29, "content": "我是个勇于冒险,突破常规的人", "factor": "开放性", "reverse_scoring": False}, {"id": 29, "content": "我是个勇于冒险,突破常规的人", "factor": "开放性", "reverse_scoring": False},
{"id": 30, "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) # 外向性维度 (F5)
{"id": 33, "content": "我喜欢参加社交与娱乐聚会", "factor": "外向性", "reverse_scoring": False}, {"id": 33, "content": "我喜欢参加社交与娱乐聚会", "factor": "外向性", "reverse_scoring": False},
{"id": 34, "content": "我对人多的聚会感到乏味", "factor": "外向性", "reverse_scoring": True}, {"id": 34, "content": "我对人多的聚会感到乏味", "factor": "外向性", "reverse_scoring": True},
@@ -50,61 +65,78 @@ PERSONALITY_QUESTIONS = [
{"id": 37, "content": "有我在的场合一般不会冷场", "factor": "外向性", "reverse_scoring": False}, {"id": 37, "content": "有我在的场合一般不会冷场", "factor": "外向性", "reverse_scoring": False},
{"id": 38, "content": "我希望成为领导者而不是被领导者", "factor": "外向性", "reverse_scoring": False}, {"id": 38, "content": "我希望成为领导者而不是被领导者", "factor": "外向性", "reverse_scoring": False},
{"id": 39, "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 = { FACTOR_DESCRIPTIONS = {
"外向性": { "外向性": {
"description": "反映个体神经系统的强弱和动力特征。外向性主要表现为个体在人际交往和社交活动中的倾向性,包括对社交活动的兴趣、对人群的态度、社交互动中的主动程度以及在群体中的影响力。高分者倾向于积极参与社交活动,乐于与人交往,善于表达自我,并往往在群体中发挥领导作用;低分者则倾向于独处,不喜欢热闹的社交场合,表现出内向、安静的特征。", "description": "反映个体神经系统的强弱和动力特征。外向性主要表现为个体在人际交往和社交活动中的倾向性,"
"包括对社交活动的兴趣、"
"对人群的态度、社交互动中的主动程度以及在群体中的影响力。高分者倾向于积极参与社交活动,乐于与人交往,善于表达自我,"
"并往往在群体中发挥领导作用;低分者则倾向于独处,不喜欢热闹的社交场合,表现出内向、安静的特征。",
"trait_words": ["热情", "活力", "社交", "主动"], "trait_words": ["热情", "活力", "社交", "主动"],
"subfactors": { "subfactors": {
"合群性": "个体愿意与他人聚在一起,即接近人群的倾向;高分表现乐群、好交际,低分表现封闭、独处", "合群性": "个体愿意与他人聚在一起,即接近人群的倾向;高分表现乐群、好交际,低分表现封闭、独处",
"热情": "个体对待别人时所表现出的态度;高分表现热情好客,低分表现冷淡", "热情": "个体对待别人时所表现出的态度;高分表现热情好客,低分表现冷淡",
"支配性": "个体喜欢指使、操纵他人,倾向于领导别人的特点;高分表现好强、发号施令,低分表现顺从、低调", "支配性": "个体喜欢指使、操纵他人,倾向于领导别人的特点;高分表现好强、发号施令,低分表现顺从、低调",
"活跃": "个体精力充沛,活跃、主动性等特点;高分表现活跃,低分表现安静" "活跃": "个体精力充沛,活跃、主动性等特点;高分表现活跃,低分表现安静",
} },
}, },
"神经质": { "神经质": {
"description": "反映个体情绪的状态和体验内心苦恼的倾向性。这个维度主要关注个体在面对压力、挫折和日常生活挑战时的情绪稳定性和适应能力。它包含了对焦虑、抑郁、愤怒等负面情绪的敏感程度,以及个体对这些情绪的调节和控制能力。高分者容易体验负面情绪,对压力较为敏感,情绪波动较大;低分者则表现出较强的情绪稳定性,能够较好地应对压力和挫折。", "description": "反映个体情绪的状态和体验内心苦恼的倾向性。这个维度主要关注个体在面对压力、"
"挫折和日常生活挑战时的情绪稳定性和适应能力。它包含了对焦虑、抑郁、愤怒等负面情绪的敏感程度,"
"以及个体对这些情绪的调节和控制能力。高分者容易体验负面情绪,对压力较为敏感,情绪波动较大;"
"低分者则表现出较强的情绪稳定性,能够较好地应对压力和挫折。",
"trait_words": ["稳定", "沉着", "从容", "坚韧"], "trait_words": ["稳定", "沉着", "从容", "坚韧"],
"subfactors": { "subfactors": {
"焦虑": "个体体验焦虑感的个体差异;高分表现坐立不安,低分表现平静", "焦虑": "个体体验焦虑感的个体差异;高分表现坐立不安,低分表现平静",
"抑郁": "个体体验抑郁情感的个体差异;高分表现郁郁寡欢,低分表现平静", "抑郁": "个体体验抑郁情感的个体差异;高分表现郁郁寡欢,低分表现平静",
"敏感多疑": "个体常常关注自己的内心活动,行为和过于意识人对自己的看法、评价;高分表现敏感多疑,低分表现淡定、自信", "敏感多疑": "个体常常关注自己的内心活动,行为和过于意识人对自己的看法、评价;高分表现敏感多疑,"
"低分表现淡定、自信",
"脆弱性": "个体在危机或困难面前无力、脆弱的特点;高分表现无能、易受伤、逃避,低分表现坚强", "脆弱性": "个体在危机或困难面前无力、脆弱的特点;高分表现无能、易受伤、逃避,低分表现坚强",
"愤怒-敌意": "个体准备体验愤怒,及相关情绪的状态;高分表现暴躁易怒,低分表现平静" "愤怒-敌意": "个体准备体验愤怒,及相关情绪的状态;高分表现暴躁易怒,低分表现平静",
} },
}, },
"严谨性": { "严谨性": {
"description": "反映个体在目标导向行为上的组织、坚持和动机特征。这个维度体现了个体在工作、学习等目标性活动中的自我约束和行为管理能力。它涉及到个体的责任感、自律性、计划性、条理性以及完成任务的态度。高分者往往表现出强烈的责任心、良好的组织能力、谨慎的决策风格和持续的努力精神;低分者则可能表现出随意性强、缺乏规划、做事马虎或易放弃的特点。", "description": "反映个体在目标导向行为上的组织、坚持和动机特征。这个维度体现了个体在工作、"
"学习等目标性活动中的自我约束和行为管理能力。它涉及到个体的责任感、自律性、计划性、条理性以及完成任务的态度。"
"高分者往往表现出强烈的责任心、良好的组织能力、谨慎的决策风格和持续的努力精神;低分者则可能表现出随意性强、"
"缺乏规划、做事马虎或易放弃的特点。",
"trait_words": ["负责", "自律", "条理", "勤奋"], "trait_words": ["负责", "自律", "条理", "勤奋"],
"subfactors": { "subfactors": {
"责任心": "个体对待任务和他人认真负责,以及对自己承诺的信守;高分表现有责任心、负责任,低分表现推卸责任、逃避处罚", "责任心": "个体对待任务和他人认真负责,以及对自己承诺的信守;高分表现有责任心、负责任,"
"低分表现推卸责任、逃避处罚",
"自我控制": "个体约束自己的能力,及自始至终的坚持性;高分表现自制、有毅力,低分表现冲动、无毅力", "自我控制": "个体约束自己的能力,及自始至终的坚持性;高分表现自制、有毅力,低分表现冲动、无毅力",
"审慎性": "个体在采取具体行动前的心理状态;高分表现谨慎、小心,低分表现鲁莽、草率", "审慎性": "个体在采取具体行动前的心理状态;高分表现谨慎、小心,低分表现鲁莽、草率",
"条理性": "个体处理事务和工作的秩序,条理和逻辑性;高分表现整洁、有秩序,低分表现混乱、遗漏", "条理性": "个体处理事务和工作的秩序,条理和逻辑性;高分表现整洁、有秩序,低分表现混乱、遗漏",
"勤奋": "个体工作和学习的努力程度及为达到目标而表现出的进取精神;高分表现勤奋、刻苦,低分表现懒散" "勤奋": "个体工作和学习的努力程度及为达到目标而表现出的进取精神;高分表现勤奋、刻苦,低分表现懒散",
} },
}, },
"开放性": { "开放性": {
"description": "反映个体对新异事物、新观念和新经验的接受程度,以及在思维和行为方面的创新倾向。这个维度体现了个体在认知和体验方面的广度、深度和灵活性。它包括对艺术的欣赏能力、对知识的求知欲、想象力的丰富程度,以及对冒险和创新的态度。高分者往往具有丰富的想象力、广泛的兴趣、开放的思维方式和创新的倾向;低分者则倾向于保守、传统,喜欢熟悉和常规的事物。", "description": "反映个体对新异事物、新观念和新经验的接受程度,以及在思维和行为方面的创新倾向。"
"这个维度体现了个体在认知和体验方面的广度、深度和灵活性。它包括对艺术的欣赏能力、对知识的求知欲、想象力的丰富程度,"
"以及对冒险和创新的态度。高分者往往具有丰富的想象力、广泛的兴趣、开放的思维方式和创新的倾向;低分者则倾向于保守、"
"传统,喜欢熟悉和常规的事物。",
"trait_words": ["创新", "好奇", "艺术", "冒险"], "trait_words": ["创新", "好奇", "艺术", "冒险"],
"subfactors": { "subfactors": {
"幻想": "个体富于幻想和想象的水平;高分表现想象力丰富,低分表现想象力匮乏", "幻想": "个体富于幻想和想象的水平;高分表现想象力丰富,低分表现想象力匮乏",
"审美": "个体对于艺术和美的敏感与热爱程度;高分表现富有艺术气息,低分表现一般对艺术不敏感", "审美": "个体对于艺术和美的敏感与热爱程度;高分表现富有艺术气息,低分表现一般对艺术不敏感",
"好奇心": "个体对未知事物的态度;高分表现兴趣广泛、好奇心浓,低分表现兴趣少、无好奇心", "好奇心": "个体对未知事物的态度;高分表现兴趣广泛、好奇心浓,低分表现兴趣少、无好奇心",
"冒险精神": "个体愿意尝试有风险活动的个体差异;高分表现好冒险,低分表现保守", "冒险精神": "个体愿意尝试有风险活动的个体差异;高分表现好冒险,低分表现保守",
"价值观念": "个体对新事物、新观念、怪异想法的态度;高分表现开放、坦然接受新事物,低分则相反" "价值观念": "个体对新事物、新观念、怪异想法的态度;高分表现开放、坦然接受新事物,低分则相反",
} },
}, },
"宜人性": { "宜人性": {
"description": "反映个体在人际关系中的亲和倾向,体现了对他人的关心、同情和合作意愿。这个维度主要关注个体与他人互动时的态度和行为特征,包括对他人的信任程度、同理心水平、助人意愿以及在人际冲突中的处理方式。高分者通常表现出友善、富有同情心、乐于助人的特质,善于与他人建立和谐关系;低分者则可能表现出较少的人际关注,在社交互动中更注重自身利益,较少考虑他人感受。", "description": "反映个体在人际关系中的亲和倾向,体现了对他人的关心、同情和合作意愿。"
"这个维度主要关注个体与他人互动时的态度和行为特征,包括对他人的信任程度、同理心水平、"
"助人意愿以及在人际冲突中的处理方式。高分者通常表现出友善、富有同情心、乐于助人的特质,善于与他人建立和谐关系;"
"低分者则可能表现出较少的人际关注,在社交互动中更注重自身利益,较少考虑他人感受。",
"trait_words": ["友善", "同理", "信任", "合作"], "trait_words": ["友善", "同理", "信任", "合作"],
"subfactors": { "subfactors": {
"信任": "个体对他人和/或他人言论的相信程度;高分表现信任他人,低分表现怀疑", "信任": "个体对他人和/或他人言论的相信程度;高分表现信任他人,低分表现怀疑",
"体贴": "个体对别人的兴趣和需要的关注程度;高分表现体贴、温存,低分表现冷漠、不在乎", "体贴": "个体对别人的兴趣和需要的关注程度;高分表现体贴、温存,低分表现冷漠、不在乎",
"同情": "个体对处于不利地位的人或物的态度;高分表现富有同情心,低分表现冷漠" "同情": "个体对处于不利地位的人或物的态度;高分表现富有同情心,低分表现冷漠",
} },
} },
} }

View File

@@ -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]: 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 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: 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 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 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.''' of individuality of a humanized social agent when users interact with the machine."""
from typing import Dict, List from typing import Dict, List
import json import json
@@ -13,9 +15,9 @@ from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
import sys import sys
''' """
第一种方案:基于情景评估的人格测定 第一种方案:基于情景评估的人格测定
''' """
current_dir = Path(__file__).resolve().parent current_dir = Path(__file__).resolve().parent
project_root = current_dir.parent.parent.parent project_root = current_dir.parent.parent.parent
env_path = project_root / ".env.prod" 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__), "../../..")) root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
sys.path.append(root_path) sys.path.append(root_path)
from src.plugins.personality.scene import get_scene_by_factor,get_all_scenes,PERSONALITY_SCENES from src.plugins.personality.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa: E402
from src.plugins.personality.questionnaire import PERSONALITY_QUESTIONS,FACTOR_DESCRIPTIONS from src.plugins.personality.questionnaire import FACTOR_DESCRIPTIONS # noqa: E402
from src.plugins.personality.offline_llm import LLMModel from src.plugins.personality.offline_llm import LLMModel # noqa: E402
# 加载环境变量 # 加载环境变量
if env_path.exists(): if env_path.exists():
@@ -49,6 +51,7 @@ class PersonalityEvaluator_direct:
# 从每个维度选择3个场景 # 从每个维度选择3个场景
import random import random
scene_keys = list(scenes.keys()) scene_keys = list(scenes.keys())
selected_scenes = random.sample(scene_keys, min(3, len(scene_keys))) selected_scenes = random.sample(scene_keys, min(3, len(scene_keys)))
@@ -60,11 +63,9 @@ class PersonalityEvaluator_direct:
other_traits = [t for t in PERSONALITY_SCENES if t != trait] other_traits = [t for t in PERSONALITY_SCENES if t != trait]
secondary_trait = random.choice(other_traits) secondary_trait = random.choice(other_traits)
self.scenarios.append({ self.scenarios.append(
"场景": scene["scenario"], {"场景": scene["scenario"], "评估维度": [trait, secondary_trait], "场景编号": scene_key}
"评估维度": [trait, secondary_trait], )
"场景编号": scene_key
})
self.llm = LLMModel() self.llm = LLMModel()
@@ -178,11 +179,7 @@ def main():
print(f"测试场景数:{dimension_counts[trait]}") print(f"测试场景数:{dimension_counts[trait]}")
# 保存结果 # 保存结果
result = { result = {"final_scores": final_scores, "dimension_counts": dimension_counts, "scenarios": evaluator.scenarios}
"final_scores": final_scores,
"dimension_counts": dimension_counts,
"scenarios": evaluator.scenarios
}
# 确保目录存在 # 确保目录存在
os.makedirs("results", exist_ok=True) os.makedirs("results", exist_ok=True)

View File

@@ -1,4 +1,4 @@
from typing import Dict, List from typing import Dict
PERSONALITY_SCENES = { PERSONALITY_SCENES = {
"外向性": { "外向性": {
@@ -8,7 +8,7 @@ PERSONALITY_SCENES = {
同事:「嗨!你是新来的同事吧?我是市场部的小林。」 同事:「嗨!你是新来的同事吧?我是市场部的小林。」
同事看起来很友善,还主动介绍说:「待会午饭时间,我们部门有几个人准备一起去楼下新开的餐厅,你要一起来吗?可以认识一下其他同事。」""", 同事看起来很友善,还主动介绍说:「待会午饭时间,我们部门有几个人准备一起去楼下新开的餐厅,你要一起来吗?可以认识一下其他同事。」""",
"explanation": "这个场景通过职场社交情境,观察个体对于新环境、新社交圈的态度和反应倾向。" "explanation": "这个场景通过职场社交情境,观察个体对于新环境、新社交圈的态度和反应倾向。",
}, },
"场景2": { "场景2": {
"scenario": """在大学班级群里,班长发起了一个组织班级联谊活动的投票: "scenario": """在大学班级群里,班长发起了一个组织班级联谊活动的投票:
@@ -16,7 +16,7 @@ PERSONALITY_SCENES = {
班长「大家好下周末我们准备举办一次班级联谊活动地点在学校附近的KTV。想请大家报名参加也欢迎大家邀请其他班级的同学 班长「大家好下周末我们准备举办一次班级联谊活动地点在学校附近的KTV。想请大家报名参加也欢迎大家邀请其他班级的同学
已经有几个同学在群里积极响应,有人@你问你要不要一起参加。""", 已经有几个同学在群里积极响应,有人@你问你要不要一起参加。""",
"explanation": "通过班级活动场景,观察个体对群体社交活动的参与意愿。" "explanation": "通过班级活动场景,观察个体对群体社交活动的参与意愿。",
}, },
"场景3": { "场景3": {
"scenario": """你在社交平台上发布了一条动态,收到了很多陌生网友的评论和私信: "scenario": """你在社交平台上发布了一条动态,收到了很多陌生网友的评论和私信:
@@ -24,13 +24,14 @@ PERSONALITY_SCENES = {
网友A「你说的这个观点很有意思想和你多交流一下。」 网友A「你说的这个观点很有意思想和你多交流一下。」
网友B「我也对这个话题很感兴趣要不要建个群一起讨论""", 网友B「我也对这个话题很感兴趣要不要建个群一起讨论""",
"explanation": "通过网络社交场景,观察个体对线上社交的态度。" "explanation": "通过网络社交场景,观察个体对线上社交的态度。",
}, },
"场景4": { "场景4": {
"scenario": """你暗恋的对象今天主动来找你: "scenario": """你暗恋的对象今天主动来找你:
对方:「那个...我最近在准备一个演讲比赛,听说你口才很好。能不能请你帮我看看演讲稿,顺便给我一些建议?如果你有时间的话,可以一起吃个饭聊聊。」""", 对方:「那个...我最近在准备一个演讲比赛,听说你口才很好。能不能请你帮我看看演讲稿,顺便给我一些建议?"""
"explanation": "通过恋爱情境,观察个体在面对心仪对象时的社交表现。" """如果你有时间的话,可以一起吃个饭聊聊。」""",
"explanation": "通过恋爱情境,观察个体在面对心仪对象时的社交表现。",
}, },
"场景5": { "场景5": {
"scenario": """在一次线下读书会上,主持人突然点名让你分享读后感: "scenario": """在一次线下读书会上,主持人突然点名让你分享读后感:
@@ -38,18 +39,18 @@ PERSONALITY_SCENES = {
主持人:「听说你对这本书很有见解,能不能和大家分享一下你的想法?」 主持人:「听说你对这本书很有见解,能不能和大家分享一下你的想法?」
现场有二十多个陌生的读书爱好者,都期待地看着你。""", 现场有二十多个陌生的读书爱好者,都期待地看着你。""",
"explanation": "通过即兴发言场景,观察个体的社交表现欲和公众表达能力。" "explanation": "通过即兴发言场景,观察个体的社交表现欲和公众表达能力。",
} },
}, },
"神经质": { "神经质": {
"场景1": { "场景1": {
"scenario": """你正在准备一个重要的项目演示,这关系到你的晋升机会。就在演示前30分钟你收到了主管发来的消息 "scenario": """你正在准备一个重要的项目演示,这关系到你的晋升机会。"""
"""就在演示前30分钟你收到了主管发来的消息
主管「临时有个变动CEO也会来听你的演示。他对这个项目特别感兴趣。」 主管「临时有个变动CEO也会来听你的演示。他对这个项目特别感兴趣。」
正当你准备回复时主管又发来一条「对了能不能把演示时间压缩到15分钟CEO下午还有其他安排。你之前准备的是30分钟的版本对吧""", 正当你准备回复时主管又发来一条「对了能不能把演示时间压缩到15分钟CEO下午还有其他安排。你之前准备的是30分钟的版本对吧""",
"explanation": "这个场景通过突发的压力情境,观察个体在面对计划外变化时的情绪反应和调节能力。" "explanation": "这个场景通过突发的压力情境,观察个体在面对计划外变化时的情绪反应和调节能力。",
}, },
"场景2": { "场景2": {
"scenario": """期末考试前一天晚上,你收到了好朋友发来的消息: "scenario": """期末考试前一天晚上,你收到了好朋友发来的消息:
@@ -57,7 +58,7 @@ PERSONALITY_SCENES = {
好朋友:「不好意思这么晚打扰你...我看你平时成绩很好,能不能帮我解答几个问题?我真的很担心明天的考试。」 好朋友:「不好意思这么晚打扰你...我看你平时成绩很好,能不能帮我解答几个问题?我真的很担心明天的考试。」
你看了看时间已经是晚上11点而你原本计划的复习还没完成。""", 你看了看时间已经是晚上11点而你原本计划的复习还没完成。""",
"explanation": "通过考试压力场景,观察个体在时间紧张时的情绪管理。" "explanation": "通过考试压力场景,观察个体在时间紧张时的情绪管理。",
}, },
"场景3": { "场景3": {
"scenario": """你在社交媒体上发表的一个观点引发了争议,有不少人开始批评你: "scenario": """你在社交媒体上发表的一个观点引发了争议,有不少人开始批评你:
@@ -67,7 +68,7 @@ PERSONALITY_SCENES = {
网友B「建议楼主先去补补课再来发言。」 网友B「建议楼主先去补补课再来发言。」
评论区里的负面评论越来越多,还有人开始人身攻击。""", 评论区里的负面评论越来越多,还有人开始人身攻击。""",
"explanation": "通过网络争议场景,观察个体面对批评时的心理承受能力。" "explanation": "通过网络争议场景,观察个体面对批评时的心理承受能力。",
}, },
"场景4": { "场景4": {
"scenario": """你和恋人约好今天一起看电影,但在约定时间前半小时,对方发来消息: "scenario": """你和恋人约好今天一起看电影,但在约定时间前半小时,对方发来消息:
@@ -77,7 +78,7 @@ PERSONALITY_SCENES = {
二十分钟后,对方又发来消息:「可能要再等等,抱歉!」 二十分钟后,对方又发来消息:「可能要再等等,抱歉!」
电影快要开始了,但对方还是没有出现。""", 电影快要开始了,但对方还是没有出现。""",
"explanation": "通过恋爱情境,观察个体对不确定性的忍耐程度。" "explanation": "通过恋爱情境,观察个体对不确定性的忍耐程度。",
}, },
"场景5": { "场景5": {
"scenario": """在一次重要的小组展示中,你的组员在演示途中突然卡壳了: "scenario": """在一次重要的小组展示中,你的组员在演示途中突然卡壳了:
@@ -85,10 +86,9 @@ PERSONALITY_SCENES = {
组员小声对你说:「我忘词了,接下来的部分是什么来着...」 组员小声对你说:「我忘词了,接下来的部分是什么来着...」
台下的老师和同学都在等待,气氛有些尴尬。""", 台下的老师和同学都在等待,气氛有些尴尬。""",
"explanation": "通过公开场合的突发状况,观察个体的应急反应和压力处理能力。" "explanation": "通过公开场合的突发状况,观察个体的应急反应和压力处理能力。",
} },
}, },
"严谨性": { "严谨性": {
"场景1": { "场景1": {
"scenario": """你是团队的项目负责人,刚刚接手了一个为期两个月的重要项目。在第一次团队会议上: "scenario": """你是团队的项目负责人,刚刚接手了一个为期两个月的重要项目。在第一次团队会议上:
@@ -98,7 +98,7 @@ PERSONALITY_SCENES = {
小张:「要不要先列个时间表?不过感觉太详细的计划也没必要,点到为止就行。」 小张:「要不要先列个时间表?不过感觉太详细的计划也没必要,点到为止就行。」
小李:「客户那边说如果能提前完成有奖励,我觉得我们可以先做快一点的部分。」""", 小李:「客户那边说如果能提前完成有奖励,我觉得我们可以先做快一点的部分。」""",
"explanation": "这个场景通过项目管理情境,体现个体在工作方法、计划性和责任心方面的特征。" "explanation": "这个场景通过项目管理情境,体现个体在工作方法、计划性和责任心方面的特征。",
}, },
"场景2": { "场景2": {
"scenario": """期末小组作业,组长让大家分工完成一份研究报告。在截止日期前三天: "scenario": """期末小组作业,组长让大家分工完成一份研究报告。在截止日期前三天:
@@ -108,7 +108,7 @@ PERSONALITY_SCENES = {
组员B「我这边可能还要一天才能完成最近太忙了。」 组员B「我这边可能还要一天才能完成最近太忙了。」
组员C发来一份没有任何引用出处、可能存在抄袭的内容「我写完了你们看看怎么样""", 组员C发来一份没有任何引用出处、可能存在抄袭的内容「我写完了你们看看怎么样""",
"explanation": "通过学习场景,观察个体对学术规范和质量要求的重视程度。" "explanation": "通过学习场景,观察个体对学术规范和质量要求的重视程度。",
}, },
"场景3": { "场景3": {
"scenario": """你在一个兴趣小组的群聊中,大家正在讨论举办一次线下活动: "scenario": """你在一个兴趣小组的群聊中,大家正在讨论举办一次线下活动:
@@ -118,7 +118,7 @@ PERSONALITY_SCENES = {
成员B「对啊随意一点挺好的。」 成员B「对啊随意一点挺好的。」
成员C「人来了自然就热闹了。」""", 成员C「人来了自然就热闹了。」""",
"explanation": "通过活动组织场景,观察个体对活动计划的态度。" "explanation": "通过活动组织场景,观察个体对活动计划的态度。",
}, },
"场景4": { "场景4": {
"scenario": """你和恋人计划一起去旅游,对方说: "scenario": """你和恋人计划一起去旅游,对方说:
@@ -126,7 +126,7 @@ PERSONALITY_SCENES = {
恋人:「我们就随心而行吧!订个目的地,其他的到了再说,这样更有意思。」 恋人:「我们就随心而行吧!订个目的地,其他的到了再说,这样更有意思。」
距离出发还有一周时间,但机票、住宿和具体行程都还没有确定。""", 距离出发还有一周时间,但机票、住宿和具体行程都还没有确定。""",
"explanation": "通过旅行规划场景,观察个体的计划性和对不确定性的接受程度。" "explanation": "通过旅行规划场景,观察个体的计划性和对不确定性的接受程度。",
}, },
"场景5": { "场景5": {
"scenario": """在一个重要的团队项目中,你发现一个同事的工作存在明显错误: "scenario": """在一个重要的团队项目中,你发现一个同事的工作存在明显错误:
@@ -134,18 +134,19 @@ PERSONALITY_SCENES = {
同事:「差不多就行了,反正领导也看不出来。」 同事:「差不多就行了,反正领导也看不出来。」
这个错误可能不会立即造成问题,但长期来看可能会影响项目质量。""", 这个错误可能不会立即造成问题,但长期来看可能会影响项目质量。""",
"explanation": "通过工作质量场景,观察个体对细节和标准的坚持程度。" "explanation": "通过工作质量场景,观察个体对细节和标准的坚持程度。",
} },
}, },
"开放性": { "开放性": {
"场景1": { "场景1": {
"scenario": """周末下午,你的好友小美兴致勃勃地给你打电话: "scenario": """周末下午,你的好友小美兴致勃勃地给你打电话:
小美:「我刚发现一个特别有意思的沉浸式艺术展!不是传统那种挂画的展览,而是把整个空间都变成了艺术品。观众要穿特制的服装还要带上VR眼镜好像还有AI实时互动 小美:「我刚发现一个特别有意思的沉浸式艺术展!不是传统那种挂画的展览,而是把整个空间都变成了艺术品。"""
"""观众要穿特制的服装还要带上VR眼镜好像还有AI实时互动
小美继续说:「虽然票价不便宜,但听说体验很独特。网上评价两极分化,有人说是前所未有的艺术革新,也有人说是哗众取宠。要不要周末一起去体验一下?」""", 小美继续说:「虽然票价不便宜,但听说体验很独特。网上评价两极分化,有人说是前所未有的艺术革新,也有人说是哗众取宠。"""
"explanation": "这个场景通过新型艺术体验,反映个体对创新事物的接受程度和尝试意愿。" """要不要周末一起去体验一下?」""",
"explanation": "这个场景通过新型艺术体验,反映个体对创新事物的接受程度和尝试意愿。",
}, },
"场景2": { "场景2": {
"scenario": """在一节创意写作课上,老师提出了一个特别的作业: "scenario": """在一节创意写作课上,老师提出了一个特别的作业:
@@ -153,15 +154,16 @@ PERSONALITY_SCENES = {
老师「下周的作业是用AI写作工具协助创作一篇小说。你们可以自由探索如何与AI合作打破传统写作方式。」 老师「下周的作业是用AI写作工具协助创作一篇小说。你们可以自由探索如何与AI合作打破传统写作方式。」
班上随即展开了激烈讨论,有人认为这是对创作的亵渎,也有人对这种新形式感到兴奋。""", 班上随即展开了激烈讨论,有人认为这是对创作的亵渎,也有人对这种新形式感到兴奋。""",
"explanation": "通过新技术应用场景,观察个体对创新学习方式的态度。" "explanation": "通过新技术应用场景,观察个体对创新学习方式的态度。",
}, },
"场景3": { "场景3": {
"scenario": """在社交媒体上,你看到一个朋友分享了一种新的生活方式: "scenario": """在社交媒体上,你看到一个朋友分享了一种新的生活方式:
「最近我在尝试'数字游牧'生活,就是一边远程工作一边环游世界。没有固定住所,住青旅或短租,认识来自世界各地的朋友。虽然有时会很不稳定,但这种自由的生活方式真的很棒!」 「最近我在尝试'数字游牧'生活,就是一边远程工作一边环游世界。"""
"""没有固定住所,住青旅或短租,认识来自世界各地的朋友。虽然有时会很不稳定,但这种自由的生活方式真的很棒!」
评论区里争论不断,有人向往这种生活,也有人觉得太冒险。""", 评论区里争论不断,有人向往这种生活,也有人觉得太冒险。""",
"explanation": "通过另类生活方式,观察个体对非传统选择的态度。" "explanation": "通过另类生活方式,观察个体对非传统选择的态度。",
}, },
"场景4": { "场景4": {
"scenario": """你的恋人突然提出了一个想法: "scenario": """你的恋人突然提出了一个想法:
@@ -169,7 +171,7 @@ PERSONALITY_SCENES = {
恋人:「我们要不要尝试一下开放式关系?就是在保持彼此关系的同时,也允许和其他人发展感情。现在国外很多年轻人都这样。」 恋人:「我们要不要尝试一下开放式关系?就是在保持彼此关系的同时,也允许和其他人发展感情。现在国外很多年轻人都这样。」
这个提议让你感到意外,你之前从未考虑过这种可能性。""", 这个提议让你感到意外,你之前从未考虑过这种可能性。""",
"explanation": "通过感情观念场景,观察个体对非传统关系模式的接受度。" "explanation": "通过感情观念场景,观察个体对非传统关系模式的接受度。",
}, },
"场景5": { "场景5": {
"scenario": """在一次朋友聚会上,大家正在讨论未来职业规划: "scenario": """在一次朋友聚会上,大家正在讨论未来职业规划:
@@ -179,10 +181,9 @@ PERSONALITY_SCENES = {
朋友B「我想去学习生物科技准备转行做人造肉研发。」 朋友B「我想去学习生物科技准备转行做人造肉研发。」
朋友C「我在考虑加入一个区块链创业项目虽然风险很大。」""", 朋友C「我在考虑加入一个区块链创业项目虽然风险很大。」""",
"explanation": "通过职业选择场景,观察个体对新兴领域的探索意愿。" "explanation": "通过职业选择场景,观察个体对新兴领域的探索意愿。",
} },
}, },
"宜人性": { "宜人性": {
"场景1": { "场景1": {
"scenario": """在回家的公交车上,你遇到这样一幕: "scenario": """在回家的公交车上,你遇到这样一幕:
@@ -194,7 +195,7 @@ PERSONALITY_SCENES = {
年轻人B「现在的老年人真是...我看她包里还有菜,肯定是去菜市场买完菜回来的,这么多人都不知道叫子女开车接送。」 年轻人B「现在的老年人真是...我看她包里还有菜,肯定是去菜市场买完菜回来的,这么多人都不知道叫子女开车接送。」
就在这时,老奶奶一个趔趄,差点摔倒。她扶住了扶手,但包里的东西洒了一些出来。""", 就在这时,老奶奶一个趔趄,差点摔倒。她扶住了扶手,但包里的东西洒了一些出来。""",
"explanation": "这个场景通过公共场合的助人情境,体现个体的同理心和对他人需求的关注程度。" "explanation": "这个场景通过公共场合的助人情境,体现个体的同理心和对他人需求的关注程度。",
}, },
"场景2": { "场景2": {
"scenario": """在班级群里,有同学发起为生病住院的同学捐款: "scenario": """在班级群里,有同学发起为生病住院的同学捐款:
@@ -204,7 +205,7 @@ PERSONALITY_SCENES = {
同学B「我觉得这是他家里的事我们不方便参与吧。」 同学B「我觉得这是他家里的事我们不方便参与吧。」
同学C「但是都是同学一场帮帮忙也是应该的。」""", 同学C「但是都是同学一场帮帮忙也是应该的。」""",
"explanation": "通过同学互助场景,观察个体的助人意愿和同理心。" "explanation": "通过同学互助场景,观察个体的助人意愿和同理心。",
}, },
"场景3": { "场景3": {
"scenario": """在一个网络讨论组里,有人发布了求助信息: "scenario": """在一个网络讨论组里,有人发布了求助信息:
@@ -215,7 +216,7 @@ PERSONALITY_SCENES = {
「生活本来就是这样,想开点!」 「生活本来就是这样,想开点!」
「你这样子太消极了,要积极面对。」 「你这样子太消极了,要积极面对。」
「谁还没点烦心事啊,过段时间就好了。」""", 「谁还没点烦心事啊,过段时间就好了。」""",
"explanation": "通过网络互助场景,观察个体的共情能力和安慰方式。" "explanation": "通过网络互助场景,观察个体的共情能力和安慰方式。",
}, },
"场景4": { "场景4": {
"scenario": """你的恋人向你倾诉工作压力: "scenario": """你的恋人向你倾诉工作压力:
@@ -223,7 +224,7 @@ PERSONALITY_SCENES = {
恋人:「最近工作真的好累,感觉快坚持不下去了...」 恋人:「最近工作真的好累,感觉快坚持不下去了...」
但今天你也遇到了很多烦心事,心情也不太好。""", 但今天你也遇到了很多烦心事,心情也不太好。""",
"explanation": "通过感情关系场景,观察个体在自身状态不佳时的关怀能力。" "explanation": "通过感情关系场景,观察个体在自身状态不佳时的关怀能力。",
}, },
"场景5": { "场景5": {
"scenario": """在一次团队项目中,新来的同事小王因为经验不足,造成了一个严重的错误。在部门会议上: "scenario": """在一次团队项目中,新来的同事小王因为经验不足,造成了一个严重的错误。在部门会议上:
@@ -231,11 +232,12 @@ PERSONALITY_SCENES = {
主管:「这个错误造成了很大的损失,是谁负责的这部分?」 主管:「这个错误造成了很大的损失,是谁负责的这部分?」
小王看起来很紧张,欲言又止。你知道是他造成的错误,同时你也是这个项目的共同负责人。""", 小王看起来很紧张,欲言又止。你知道是他造成的错误,同时你也是这个项目的共同负责人。""",
"explanation": "通过职场情境,观察个体在面对他人过错时的态度和处理方式。" "explanation": "通过职场情境,观察个体在面对他人过错时的态度和处理方式。",
} },
} },
} }
def get_scene_by_factor(factor: str) -> Dict: def get_scene_by_factor(factor: str) -> Dict:
""" """
根据人格因子获取对应的情景测试 根据人格因子获取对应的情景测试
@@ -248,6 +250,7 @@ def get_scene_by_factor(factor: str) -> Dict:
""" """
return PERSONALITY_SCENES.get(factor, None) return PERSONALITY_SCENES.get(factor, None)
def get_all_scenes() -> Dict: def get_all_scenes() -> Dict:
""" """
获取所有情景测试 获取所有情景测试

202
webui.py
View File

@@ -4,11 +4,14 @@ import toml
import signal import signal
import sys import sys
import requests import requests
try: try:
from src.common.logger import get_module_logger from src.common.logger import get_module_logger
logger = get_module_logger("webui") logger = get_module_logger("webui")
except ImportError: except ImportError:
from loguru import logger from loguru import logger
# 检查并创建日志目录 # 检查并创建日志目录
log_dir = "logs/webui" log_dir = "logs/webui"
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
@@ -24,11 +27,13 @@ import ast
from packaging import version from packaging import version
from decimal import Decimal from decimal import Decimal
def signal_handler(signum, frame): def signal_handler(signum, frame):
"""处理 Ctrl+C 信号""" """处理 Ctrl+C 信号"""
logger.info("收到终止信号,正在关闭 Gradio 服务器...") logger.info("收到终止信号,正在关闭 Gradio 服务器...")
sys.exit(0) sys.exit(0)
# 注册信号处理器 # 注册信号处理器
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
@@ -44,10 +49,10 @@ if not os.path.exists(".env.prod"):
raise FileNotFoundError("环境配置文件 .env.prod 不存在,请检查配置文件路径") raise FileNotFoundError("环境配置文件 .env.prod 不存在,请检查配置文件路径")
config_data = toml.load("config/bot_config.toml") config_data = toml.load("config/bot_config.toml")
#增加对老版本配置文件支持 # 增加对老版本配置文件支持
LEGACY_CONFIG_VERSION = version.parse("0.0.1") LEGACY_CONFIG_VERSION = version.parse("0.0.1")
#增加最低支持版本 # 增加最低支持版本
MIN_SUPPORT_VERSION = version.parse("0.0.8") MIN_SUPPORT_VERSION = version.parse("0.0.8")
MIN_SUPPORT_MAIMAI_VERSION = version.parse("0.5.13") MIN_SUPPORT_MAIMAI_VERSION = version.parse("0.5.13")
@@ -66,7 +71,7 @@ else:
HAVE_ONLINE_STATUS_VERSION = version.parse("0.0.9") HAVE_ONLINE_STATUS_VERSION = version.parse("0.0.9")
#定义意愿模式可选项 # 定义意愿模式可选项
WILLING_MODE_CHOICES = [ WILLING_MODE_CHOICES = [
"classical", "classical",
"dynamic", "dynamic",
@@ -74,11 +79,10 @@ WILLING_MODE_CHOICES = [
] ]
# 添加WebUI配置文件版本
#添加WebUI配置文件版本
WEBUI_VERSION = version.parse("0.0.9") WEBUI_VERSION = version.parse("0.0.9")
# ============================================== # ==============================================
# env环境配置文件读取部分 # env环境配置文件读取部分
def parse_env_config(config_file): def parse_env_config(config_file):
@@ -204,7 +208,7 @@ MODEL_PROVIDER_LIST = parse_model_providers(env_config_data)
# env读取保存结束 # env读取保存结束
# ============================================== # ==============================================
#获取在线麦麦数量 # 获取在线麦麦数量
def get_online_maimbot(url="http://hyybuth.xyz:10058/api/clients/details", timeout=10): 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保存函数 # env保存函数
def save_trigger( def save_trigger(
server_address, server_address,
server_port, server_port,
final_result_list, final_result_list,
t_mongodb_host, t_mongodb_host,
t_mongodb_port, t_mongodb_port,
t_mongodb_database_name, t_mongodb_database_name,
t_console_log_level, t_console_log_level,
t_file_log_level, t_file_log_level,
t_default_console_log_level, t_default_console_log_level,
t_default_file_log_level, t_default_file_log_level,
t_api_provider, t_api_provider,
t_api_base_url, t_api_base_url,
t_api_key, t_api_key,
): ):
final_result_lists = format_list_to_str(final_result_list) final_result_lists = format_list_to_str(final_result_list)
env_config_data["env_HOST"] = server_address 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并显示警告 # 监听滑块的值变化,确保总和不超过 1并显示警告
def adjust_personality_greater_probabilities( 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 = ( total = (
Decimal(str(t_personality_1_probability)) Decimal(str(t_personality_1_probability))
+ Decimal(str(t_personality_2_probability)) + Decimal(str(t_personality_2_probability))
+ Decimal(str(t_personality_3_probability)) + Decimal(str(t_personality_3_probability))
) )
if total > Decimal("1.0"): if total > Decimal("1.0"):
warning_message = ( warning_message = (
@@ -428,12 +432,12 @@ def adjust_personality_greater_probabilities(
def adjust_personality_less_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 = ( total = (
Decimal(str(t_personality_1_probability)) Decimal(str(t_personality_1_probability))
+ Decimal(str(t_personality_2_probability)) + Decimal(str(t_personality_2_probability))
+ Decimal(str(t_personality_3_probability)) + Decimal(str(t_personality_3_probability))
) )
if total < Decimal("1.0"): if total < Decimal("1.0"):
warning_message = ( 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): def adjust_model_greater_probabilities(t_model_1_probability, t_model_2_probability, t_model_3_probability):
total = ( total = (
Decimal(str(t_model_1_probability)) + Decimal(str(t_model_1_probability)) + Decimal(str(t_model_2_probability)) + Decimal(str(t_model_3_probability))
Decimal(str(t_model_2_probability)) +
Decimal(str(t_model_3_probability))
) )
if total > Decimal("1.0"): if total > Decimal("1.0"):
warning_message = ( 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): def adjust_model_less_probabilities(t_model_1_probability, t_model_2_probability, t_model_3_probability):
total = ( total = (
Decimal(str(t_model_1_probability)) Decimal(str(t_model_1_probability)) + Decimal(str(t_model_2_probability)) + Decimal(str(t_model_3_probability))
+ Decimal(str(t_model_2_probability))
+ Decimal(str(t_model_3_probability))
) )
if total < Decimal("1.0"): if total < Decimal("1.0"):
warning_message = ( warning_message = (
@@ -474,13 +474,13 @@ def adjust_model_less_probabilities(t_model_1_probability, t_model_2_probability
# ============================================== # ==============================================
# 人格保存函数 # 人格保存函数
def save_personality_config( def save_personality_config(
t_prompt_personality_1, t_prompt_personality_1,
t_prompt_personality_2, t_prompt_personality_2,
t_prompt_personality_3, t_prompt_personality_3,
t_prompt_schedule, t_prompt_schedule,
t_personality_1_probability, t_personality_1_probability,
t_personality_2_probability, t_personality_2_probability,
t_personality_3_probability, t_personality_3_probability,
): ):
# 保存人格提示词 # 保存人格提示词
config_data["personality"]["prompt_personality"][0] = t_prompt_personality_1 config_data["personality"]["prompt_personality"][0] = t_prompt_personality_1
@@ -501,20 +501,20 @@ def save_personality_config(
def save_message_and_emoji_config( def save_message_and_emoji_config(
t_min_text_length, t_min_text_length,
t_max_context_size, t_max_context_size,
t_emoji_chance, t_emoji_chance,
t_thinking_timeout, t_thinking_timeout,
t_response_willing_amplifier, t_response_willing_amplifier,
t_response_interested_rate_amplifier, t_response_interested_rate_amplifier,
t_down_frequency_rate, t_down_frequency_rate,
t_ban_words_final_result, t_ban_words_final_result,
t_ban_msgs_regex_final_result, t_ban_msgs_regex_final_result,
t_check_interval, t_check_interval,
t_register_interval, t_register_interval,
t_auto_save, t_auto_save,
t_enable_check, t_enable_check,
t_check_prompt, t_check_prompt,
): ):
config_data["message"]["min_text_length"] = t_min_text_length config_data["message"]["min_text_length"] = t_min_text_length
config_data["message"]["max_context_size"] = t_max_context_size 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( def save_response_model_config(
t_willing_mode, t_willing_mode,
t_model_r1_probability, t_model_r1_probability,
t_model_r2_probability, t_model_r2_probability,
t_model_r3_probability, t_model_r3_probability,
t_max_response_length, t_max_response_length,
t_model1_name, t_model1_name,
t_model1_provider, t_model1_provider,
t_model1_pri_in, t_model1_pri_in,
t_model1_pri_out, t_model1_pri_out,
t_model2_name, t_model2_name,
t_model2_provider, t_model2_provider,
t_model3_name, t_model3_name,
t_model3_provider, t_model3_provider,
t_emotion_model_name, t_emotion_model_name,
t_emotion_model_provider, t_emotion_model_provider,
t_topic_judge_model_name, t_topic_judge_model_name,
t_topic_judge_model_provider, t_topic_judge_model_provider,
t_summary_by_topic_model_name, t_summary_by_topic_model_name,
t_summary_by_topic_model_provider, t_summary_by_topic_model_provider,
t_vlm_model_name, t_vlm_model_name,
t_vlm_model_provider, t_vlm_model_provider,
): ):
if PARSED_CONFIG_VERSION >= version.parse("0.0.10"): if PARSED_CONFIG_VERSION >= version.parse("0.0.10"):
config_data["willing"]["willing_mode"] = t_willing_mode config_data["willing"]["willing_mode"] = t_willing_mode
@@ -586,15 +586,15 @@ def save_response_model_config(
def save_memory_mood_config( def save_memory_mood_config(
t_build_memory_interval, t_build_memory_interval,
t_memory_compress_rate, t_memory_compress_rate,
t_forget_memory_interval, t_forget_memory_interval,
t_memory_forget_time, t_memory_forget_time,
t_memory_forget_percentage, t_memory_forget_percentage,
t_memory_ban_words_final_result, t_memory_ban_words_final_result,
t_mood_update_interval, t_mood_update_interval,
t_mood_decay_rate, t_mood_decay_rate,
t_mood_intensity_factor, t_mood_intensity_factor,
): ):
config_data["memory"]["build_memory_interval"] = t_build_memory_interval config_data["memory"]["build_memory_interval"] = t_build_memory_interval
config_data["memory"]["memory_compress_rate"] = t_memory_compress_rate config_data["memory"]["memory_compress_rate"] = t_memory_compress_rate
@@ -611,17 +611,17 @@ def save_memory_mood_config(
def save_other_config( def save_other_config(
t_keywords_reaction_enabled, t_keywords_reaction_enabled,
t_enable_advance_output, t_enable_advance_output,
t_enable_kuuki_read, t_enable_kuuki_read,
t_enable_debug_output, t_enable_debug_output,
t_enable_friend_chat, t_enable_friend_chat,
t_chinese_typo_enabled, t_chinese_typo_enabled,
t_error_rate, t_error_rate,
t_min_freq, t_min_freq,
t_tone_error_rate, t_tone_error_rate,
t_word_replace_rate, t_word_replace_rate,
t_remote_status, t_remote_status,
): ):
config_data["keywords_reaction"]["enable"] = t_keywords_reaction_enabled config_data["keywords_reaction"]["enable"] = t_keywords_reaction_enabled
config_data["others"]["enable_advance_output"] = t_enable_advance_output config_data["others"]["enable_advance_output"] = t_enable_advance_output
@@ -641,9 +641,9 @@ def save_other_config(
def save_group_config( def save_group_config(
t_talk_allowed_final_result, t_talk_allowed_final_result,
t_talk_frequency_down_final_result, t_talk_frequency_down_final_result,
t_ban_user_id_final_result, t_ban_user_id_final_result,
): ):
config_data["groups"]["talk_allowed"] = t_talk_allowed_final_result config_data["groups"]["talk_allowed"] = t_talk_allowed_final_result
config_data["groups"]["talk_frequency_down"] = t_talk_frequency_down_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( willing_mode = gr.Dropdown(
choices=WILLING_MODE_CHOICES, choices=WILLING_MODE_CHOICES,
value=config_data["willing"]["willing_mode"], value=config_data["willing"]["willing_mode"],
label="回复意愿模式" label="回复意愿模式",
) )
else: else:
willing_mode = gr.Textbox(visible=False,value="disabled") willing_mode = gr.Textbox(visible=False, value="disabled")
with gr.Row(): with gr.Row():
model_r1_probability = gr.Slider( model_r1_probability = gr.Slider(
minimum=0, minimum=0,