Merge branch 'debug' into debug
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -193,9 +193,8 @@ cython_debug/
|
||||
# jieba
|
||||
jieba.cache
|
||||
|
||||
|
||||
# vscode
|
||||
/.vscode
|
||||
# .vscode
|
||||
!.vscode/settings.json
|
||||
|
||||
# direnv
|
||||
/.direnv
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
### 部署方式
|
||||
|
||||
- 📦 **Windows 一键傻瓜式部署**:请运行项目根目录中的 ```run.bat```,部署完成后请参照后续配置指南进行配置
|
||||
- 📦 **Windows 一键傻瓜式部署**:请运行项目根目录中的 `run.bat`,部署完成后请参照后续配置指南进行配置
|
||||
|
||||
- [📦 Windows 手动部署指南 ](docs/manual_deploy_windows.md)
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
|
||||
|
||||
## 📌 注意事项
|
||||
|
||||
SengokuCola纯编程外行,面向cursor编程,很多代码史一样多多包涵
|
||||
> [!WARNING]
|
||||
> 本应用生成内容来自人工智能模型,由 AI 生成,请仔细甄别,请勿用于违反法律的用途,AI生成内容不代表本人观点和立场。
|
||||
|
||||
65
bot.py
65
bot.py
@@ -1,9 +1,12 @@
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import nonebot
|
||||
import time
|
||||
|
||||
import uvicorn
|
||||
from dotenv import load_dotenv
|
||||
from loguru import logger
|
||||
from nonebot.adapters.onebot.v11 import Adapter
|
||||
@@ -12,6 +15,8 @@ import platform
|
||||
# 获取没有加载env时的环境变量
|
||||
env_mask = {key: os.getenv(key) for key in os.environ}
|
||||
|
||||
uvicorn_server = None
|
||||
|
||||
|
||||
def easter_egg():
|
||||
# 彩蛋
|
||||
@@ -100,10 +105,12 @@ def load_logger():
|
||||
"#777777>|</> <cyan>{name:.<8}</cyan>:<cyan>{function:.<8}</cyan>:<cyan>{line: >4}</cyan> <fg "
|
||||
"#777777>-</> <level>{message}</level>",
|
||||
colorize=True,
|
||||
level=os.getenv("LOG_LEVEL", "DEBUG") # 根据环境设置日志级别,默认为INFO
|
||||
level=os.getenv("LOG_LEVEL", "INFO"), # 根据环境设置日志级别,默认为INFO
|
||||
filter=lambda record: "nonebot" not in record["name"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
def scan_provider(env_config: dict):
|
||||
provider = {}
|
||||
|
||||
@@ -138,7 +145,39 @@ def scan_provider(env_config: dict):
|
||||
raise ValueError(f"请检查 '{provider_name}' 提供商配置是否丢失 BASE_URL 或 KEY 环境变量")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
async def graceful_shutdown():
|
||||
try:
|
||||
global uvicorn_server
|
||||
if uvicorn_server:
|
||||
uvicorn_server.force_exit = True # 强制退出
|
||||
await uvicorn_server.shutdown()
|
||||
|
||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"麦麦关闭失败: {e}")
|
||||
|
||||
|
||||
async def uvicorn_main():
|
||||
global uvicorn_server
|
||||
config = uvicorn.Config(
|
||||
app="__main__:app",
|
||||
host=os.getenv("HOST", "127.0.0.1"),
|
||||
port=int(os.getenv("PORT", 8080)),
|
||||
reload=os.getenv("ENVIRONMENT") == "dev",
|
||||
timeout_graceful_shutdown=5,
|
||||
log_config=None,
|
||||
access_log=False
|
||||
)
|
||||
server = uvicorn.Server(config)
|
||||
uvicorn_server = server
|
||||
await server.serve()
|
||||
|
||||
|
||||
def raw_main():
|
||||
# 利用 TZ 环境变量设定程序工作的时区
|
||||
# 仅保证行为一致,不依赖 localtime(),实际对生产环境几乎没有作用
|
||||
if platform.system().lower() != 'windows':
|
||||
@@ -165,10 +204,30 @@ if __name__ == "__main__":
|
||||
nonebot.init(**base_config, **env_config)
|
||||
|
||||
# 注册适配器
|
||||
global driver
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(Adapter)
|
||||
|
||||
# 加载插件
|
||||
nonebot.load_plugins("src/plugins")
|
||||
|
||||
nonebot.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
raw_main()
|
||||
|
||||
global app
|
||||
app = nonebot.get_asgi()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(uvicorn_main())
|
||||
except KeyboardInterrupt:
|
||||
logger.warning("麦麦会努力做的更好的!正在停止中......")
|
||||
except Exception as e:
|
||||
logger.error(f"主程序异常: {e}")
|
||||
finally:
|
||||
loop.run_until_complete(graceful_shutdown())
|
||||
loop.close()
|
||||
logger.info("进程终止完毕,麦麦开始休眠......下次再见哦!")
|
||||
|
||||
@@ -6,8 +6,6 @@ services:
|
||||
- NAPCAT_UID=${NAPCAT_UID}
|
||||
- NAPCAT_GID=${NAPCAT_GID} # 让 NapCat 获取当前用户 GID,UID,防止权限问题
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 3001:3001
|
||||
- 6099:6099
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
@@ -19,7 +17,7 @@ services:
|
||||
mongodb:
|
||||
container_name: mongodb
|
||||
environment:
|
||||
- tz=Asia/Shanghai
|
||||
- TZ=Asia/Shanghai
|
||||
# - MONGO_INITDB_ROOT_USERNAME=your_username
|
||||
# - MONGO_INITDB_ROOT_PASSWORD=your_password
|
||||
expose:
|
||||
|
||||
20
docs/Jonathan R.md
Normal file
20
docs/Jonathan R.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Jonathan R. Wolpaw 在 “Memory in neuroscience: rhetoric versus reality.” 一文中提到,从神经科学的感觉运动假设出发,整个神经系统的功能是将经验与适当的行为联系起来,而不是单纯的信息存储。
|
||||
Jonathan R,Wolpaw. (2019). Memory in neuroscience: rhetoric versus reality.. Behavioral and cognitive neuroscience reviews(2).
|
||||
|
||||
1. **单一过程理论**
|
||||
- 单一过程理论认为,识别记忆主要是基于熟悉性这一单一因素的影响。熟悉性是指对刺激的一种自动的、无意识的感知,它可以使我们在没有回忆起具体细节的情况下,判断一个刺激是否曾经出现过。
|
||||
- 例如,在一些实验中,研究者发现被试可以在没有回忆起具体学习情境的情况下,对曾经出现过的刺激做出正确的判断,这被认为是熟悉性在起作用1。
|
||||
2. **双重过程理论**
|
||||
- 双重过程理论则认为,识别记忆是基于两个过程:回忆和熟悉性。回忆是指对过去经验的有意识的回忆,它可以使我们回忆起具体的细节和情境;熟悉性则是一种自动的、无意识的感知。
|
||||
- 该理论认为,在识别记忆中,回忆和熟悉性共同作用,使我们能够判断一个刺激是否曾经出现过。例如,在 “记得 / 知道” 范式中,被试被要求判断他们对一个刺激的记忆是基于回忆还是熟悉性。研究发现,被试可以区分这两种不同的记忆过程,这为双重过程理论提供了支持1。
|
||||
|
||||
|
||||
|
||||
1. **神经元节点与连接**:借鉴神经网络原理,将每个记忆单元视为一个神经元节点。节点之间通过连接相互关联,连接的强度代表记忆之间的关联程度。在形态学联想记忆中,具有相似形态特征的记忆节点连接强度较高。例如,苹果和橘子的记忆节点,由于在形状、都是水果等形态语义特征上相似,它们之间的连接强度大于苹果与汽车记忆节点间的连接强度。
|
||||
2. **记忆聚类与层次结构**:依据形态特征的相似性对记忆进行聚类,形成不同的记忆簇。每个记忆簇内部的记忆具有较高的相似性,而不同记忆簇之间的记忆相似性较低。同时,构建记忆的层次结构,高层次的记忆节点代表更抽象、概括的概念,低层次的记忆节点对应具体的实例。比如,“水果” 作为高层次记忆节点,连接着 “苹果”“橘子”“香蕉” 等低层次具体水果的记忆节点。
|
||||
3. **网络的动态更新**:随着新记忆的不断加入,记忆网络动态调整。新记忆节点根据其形态特征与现有网络中的节点建立连接,同时影响相关连接的强度。若新记忆与某个记忆簇的特征高度相似,则被纳入该记忆簇;若具有独特特征,则可能引发新的记忆簇的形成。例如,当系统学习到一种新的水果 “番石榴”,它会根据番石榴的形态、语义等特征,在记忆网络中找到与之最相似的区域(如水果记忆簇),并建立相应连接,同时调整周围节点连接强度以适应这一新记忆。
|
||||
|
||||
|
||||
|
||||
- **相似性联想**:该理论认为,当两个或多个事物在形态上具有相似性时,它们在记忆中会形成关联。例如,梨和苹果在形状和都是水果这一属性上有相似性,所以当我们看到梨时,很容易通过形态学联想记忆联想到苹果。这种相似性联想有助于我们对新事物进行分类和理解,当遇到一个新的类似水果时,我们可以通过与已有的水果记忆进行相似性匹配,来推测它的一些特征。
|
||||
- **时空关联性联想**:除了相似性联想,MAM 还强调时空关联性联想。如果两个事物在时间或空间上经常同时出现,它们也会在记忆中形成关联。比如,每次在公园里看到花的时候,都能听到鸟儿的叫声,那么花和鸟儿叫声的形态特征(花的视觉形态和鸟叫的听觉形态)就会在记忆中形成关联,以后听到鸟叫可能就会联想到公园里的花。
|
||||
@@ -1,63 +1,93 @@
|
||||
# 🐳 Docker 部署指南
|
||||
|
||||
## 部署步骤(推荐,但不一定是最新)
|
||||
## 部署步骤 (推荐,但不一定是最新)
|
||||
|
||||
**"更新镜像与容器"部分在本文档 [Part 6](#6-更新镜像与容器)**
|
||||
|
||||
### 0. 前提说明
|
||||
|
||||
**本文假设读者已具备一定的 Docker 基础知识。若您对 Docker 不熟悉,建议先参考相关教程或文档进行学习,或选择使用 [📦Linux手动部署指南](./manual_deploy_linux.md) 或 [📦Windows手动部署指南](./manual_deploy_windows.md) 。**
|
||||
|
||||
|
||||
### 1. 获取Docker配置文件:
|
||||
### 1. 获取Docker配置文件
|
||||
|
||||
- 建议先单独创建好一个文件夹并进入,作为工作目录
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/SengokuCola/MaiMBot/main/docker-compose.yml -O docker-compose.yml
|
||||
```
|
||||
|
||||
- 若需要启用MongoDB数据库的用户名和密码,可进入docker-compose.yml,取消MongoDB处的注释并修改变量`=`后方的值为你的用户名和密码\
|
||||
- 若需要启用MongoDB数据库的用户名和密码,可进入docker-compose.yml,取消MongoDB处的注释并修改变量旁 `=` 后方的值为你的用户名和密码\
|
||||
修改后请注意在之后配置 `.env.prod` 文件时指定MongoDB数据库的用户名密码
|
||||
|
||||
|
||||
### 2. 启动服务:
|
||||
### 2. 启动服务
|
||||
|
||||
- **!!! 请在第一次启动前确保当前工作目录下 `.env.prod` 与 `bot_config.toml` 文件存在 !!!**\
|
||||
由于Docker文件映射行为的特殊性,若宿主机的映射路径不存在,可能导致意外的目录创建,而不会创建文件,由于此处需要文件映射到文件,需提前确保文件存在且路径正确,可使用如下命令:
|
||||
|
||||
```bash
|
||||
touch .env.prod
|
||||
touch bot_config.toml
|
||||
```
|
||||
|
||||
- 启动Docker容器:
|
||||
|
||||
```bash
|
||||
NAPCAT_UID=$(id -u) NAPCAT_GID=$(id -g) docker compose up -d
|
||||
# 旧版Docker中可能找不到docker compose,请使用docker-compose工具替代
|
||||
NAPCAT_UID=$(id -u) NAPCAT_GID=$(id -g) docker-compose up -d
|
||||
```
|
||||
|
||||
- 旧版Docker中可能找不到docker compose,请使用docker-compose工具替代
|
||||
|
||||
### 3. 修改配置并重启Docker
|
||||
|
||||
### 3. 修改配置并重启Docker:
|
||||
|
||||
- 请前往 [🎀 新手配置指南](docs/installation_cute.md) 或 [⚙️ 标准配置指南](docs/installation_standard.md) 完成`.env.prod`与`bot_config.toml`配置文件的编写\
|
||||
- 请前往 [🎀新手配置指南](./installation_cute.md) 或 [⚙️标准配置指南](./installation_standard.md) 完成 `.env.prod` 与 `bot_config.toml` 配置文件的编写\
|
||||
**需要注意 `.env.prod` 中HOST处IP的填写,Docker中部署和系统中直接安装的配置会有所不同**
|
||||
|
||||
- 重启Docker容器:
|
||||
|
||||
```bash
|
||||
docker restart maimbot # 若修改过容器名称则替换maimbot为你自定的名臣
|
||||
docker restart maimbot # 若修改过容器名称则替换maimbot为你自定的名称
|
||||
```
|
||||
|
||||
- 下方命令可以但不推荐,只是同时重启NapCat、MongoDB、MaiMBot三个服务
|
||||
|
||||
```bash
|
||||
NAPCAT_UID=$(id -u) NAPCAT_GID=$(id -g) docker compose restart
|
||||
# 旧版Docker中可能找不到docker compose,请使用docker-compose工具替代
|
||||
NAPCAT_UID=$(id -u) NAPCAT_GID=$(id -g) docker-compose restart
|
||||
```
|
||||
|
||||
- 旧版Docker中可能找不到docker compose,请使用docker-compose工具替代
|
||||
|
||||
|
||||
### 4. 登入NapCat管理页添加反向WebSocket
|
||||
|
||||
- 在浏览器地址栏输入 `http://<宿主机IP>:6099/` 进入NapCat的管理Web页,添加一个Websocket客户端
|
||||
|
||||
> 网络配置 -> 新建 -> Websocket客户端
|
||||
|
||||
- Websocket客户端的名称自定,URL栏填入 `ws://maimbot:8080/onebot/v11/ws`,启用并保存即可\
|
||||
(若修改过容器名称则替换maimbot为你自定的名称)
|
||||
|
||||
|
||||
### 5. 愉快地和麦麦对话吧!
|
||||
### 5. 部署完成,愉快地和麦麦对话吧!
|
||||
|
||||
|
||||
### 6. 更新镜像与容器
|
||||
|
||||
- 拉取最新镜像
|
||||
|
||||
```bash
|
||||
docker-compose pull
|
||||
```
|
||||
|
||||
- 执行启动容器指令,该指令会自动重建镜像有更新的容器并启动
|
||||
|
||||
```bash
|
||||
NAPCAT_UID=$(id -u) NAPCAT_GID=$(id -g) docker compose up -d
|
||||
# 旧版Docker中可能找不到docker compose,请使用docker-compose工具替代
|
||||
NAPCAT_UID=$(id -u) NAPCAT_GID=$(id -g) docker-compose up -d
|
||||
```
|
||||
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
@@ -66,7 +66,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/)
|
||||
- 安装与启动: Debian参考[官方文档](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-debian/),Ubuntu参考[官方文档](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/)
|
||||
|
||||
- 默认连接本地27017端口
|
||||
---
|
||||
@@ -76,15 +76,14 @@ pip install -r requirements.txt
|
||||
|
||||
- 参考[NapCat官方文档](https://www.napcat.wiki/guide/boot/Shell#napcat-installer-linux%E4%B8%80%E9%94%AE%E4%BD%BF%E7%94%A8%E8%84%9A%E6%9C%AC-%E6%94%AF%E6%8C%81ubuntu-20-debian-10-centos9)安装
|
||||
|
||||
- 使用QQ小号登录,添加反向WS地址:
|
||||
`ws://127.0.0.1:8080/onebot/v11/ws`
|
||||
- 使用QQ小号登录,添加反向WS地址: `ws://127.0.0.1:8080/onebot/v11/ws`
|
||||
|
||||
---
|
||||
|
||||
## 配置文件设置
|
||||
### 5️⃣ **配置文件设置,让麦麦Bot正常工作**
|
||||
- 修改环境配置文件:`.env.prod`
|
||||
- 修改机器人配置文件:`bot_config.toml`
|
||||
- 修改环境配置文件: `.env.prod`
|
||||
- 修改机器人配置文件: `bot_config.toml`
|
||||
|
||||
|
||||
---
|
||||
@@ -107,9 +106,9 @@ python3 bot.py
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
🔧 权限问题:在命令前加`sudo`
|
||||
🔌 端口占用:使用`sudo lsof -i :8080`查看端口占用
|
||||
🛡️ 防火墙:确保8080/27017端口开放
|
||||
🔧 权限问题: 在命令前加 `sudo`
|
||||
🔌 端口占用: 使用 `sudo lsof -i :8080` 查看端口占用
|
||||
🛡️ 防火墙: 确保8080/27017端口开放
|
||||
```bash
|
||||
sudo ufw allow 8080/tcp
|
||||
sudo ufw allow 27017/tcp
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
在创建虚拟环境之前,请确保你的电脑上安装了Python 3.9及以上版本。如果没有,可以按以下步骤安装:
|
||||
|
||||
1. 访问Python官网下载页面:https://www.python.org/downloads/release/python-3913/
|
||||
1. 访问Python官网下载页面: https://www.python.org/downloads/release/python-3913/
|
||||
2. 下载Windows安装程序 (64-bit): `python-3.9.13-amd64.exe`
|
||||
3. 运行安装程序,并确保勾选"Add Python 3.9 to PATH"选项
|
||||
4. 点击"Install Now"开始安装
|
||||
@@ -79,11 +79,11 @@ pip install -r requirements.txt
|
||||
|
||||
### 3️⃣ **配置NapCat,让麦麦bot与qq取得联系**
|
||||
- 安装并登录NapCat(用你的qq小号)
|
||||
- 添加反向WS:`ws://127.0.0.1:8080/onebot/v11/ws`
|
||||
- 添加反向WS: `ws://127.0.0.1:8080/onebot/v11/ws`
|
||||
|
||||
### 4️⃣ **配置文件设置,让麦麦Bot正常工作**
|
||||
- 修改环境配置文件:`.env.prod`
|
||||
- 修改机器人配置文件:`bot_config.toml`
|
||||
- 修改环境配置文件: `.env.prod`
|
||||
- 修改机器人配置文件: `bot_config.toml`
|
||||
|
||||
### 5️⃣ **启动麦麦机器人**
|
||||
- 打开命令行,cd到对应路径
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
pythonEnv = pkgs.python3.withPackages (
|
||||
ps: with ps; [
|
||||
ruff
|
||||
pymongo
|
||||
python-dotenv
|
||||
pydantic
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[project]
|
||||
name = "Megbot"
|
||||
version = "0.1.0"
|
||||
description = "New Bot Project"
|
||||
|
||||
[tool.nonebot]
|
||||
plugins = ["src.plugins.chat"]
|
||||
plugin_dirs = ["src/plugins"]
|
||||
|
||||
[tool.ruff]
|
||||
# 设置 Python 版本
|
||||
target-version = "py39"
|
||||
|
||||
# 启用的规则
|
||||
select = [
|
||||
"E", # pycodestyle 错误
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"B", # flake8-bugbear
|
||||
]
|
||||
|
||||
# 行长度设置
|
||||
line-length = 88
|
||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
40
ruff.toml
Normal file
40
ruff.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
include = ["*.py"]
|
||||
|
||||
# 行长度设置
|
||||
line-length = 120
|
||||
|
||||
[lint]
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
# 如果一个变量的名称以下划线开头,即使它未被使用,也不应该被视为错误或警告。
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
# 启用的规则
|
||||
select = [
|
||||
"E", # pycodestyle 错误
|
||||
"F", # pyflakes
|
||||
"B", # flake8-bugbear
|
||||
]
|
||||
|
||||
ignore = ["E711"]
|
||||
|
||||
[format]
|
||||
docstring-code-format = true
|
||||
indent-style = "space"
|
||||
|
||||
|
||||
# 使用双引号表示字符串
|
||||
quote-style = "double"
|
||||
|
||||
# 尊重魔法尾随逗号
|
||||
# 例如:
|
||||
# items = [
|
||||
# "apple",
|
||||
# "banana",
|
||||
# "cherry",
|
||||
# ]
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
# 自动检测合适的换行符
|
||||
line-ending = "auto"
|
||||
@@ -87,7 +87,7 @@ class ReasoningGUI:
|
||||
self.db = Database.get_instance().db
|
||||
logger.success("数据库初始化成功")
|
||||
except Exception:
|
||||
logger.exception(f"数据库初始化失败")
|
||||
logger.exception("数据库初始化失败")
|
||||
sys.exit(1)
|
||||
|
||||
# 存储群组数据
|
||||
@@ -342,7 +342,7 @@ class ReasoningGUI:
|
||||
'group_id': self.selected_group_id
|
||||
})
|
||||
except Exception:
|
||||
logger.exception(f"自动更新出错")
|
||||
logger.exception("自动更新出错")
|
||||
|
||||
# 每5秒更新一次
|
||||
time.sleep(5)
|
||||
|
||||
@@ -121,9 +121,9 @@ async def build_memory_task():
|
||||
@scheduler.scheduled_job("interval", seconds=global_config.forget_memory_interval, id="forget_memory")
|
||||
async def forget_memory_task():
|
||||
"""每30秒执行一次记忆构建"""
|
||||
# print("\033[1;32m[记忆遗忘]\033[0m 开始遗忘记忆...")
|
||||
# await hippocampus.operation_forget_topic(percentage=0.1)
|
||||
# print("\033[1;32m[记忆遗忘]\033[0m 记忆遗忘完成")
|
||||
print("\033[1;32m[记忆遗忘]\033[0m 开始遗忘记忆...")
|
||||
await hippocampus.operation_forget_topic(percentage=0.1)
|
||||
print("\033[1;32m[记忆遗忘]\033[0m 记忆遗忘完成")
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", seconds=global_config.build_memory_interval + 10, id="merge_memory")
|
||||
|
||||
@@ -129,7 +129,7 @@ class ChatBot:
|
||||
|
||||
# 如果找不到思考消息,直接返回
|
||||
if not thinking_message:
|
||||
logger.warning(f"未找到对应的思考消息,可能已超时被移除")
|
||||
logger.warning("未找到对应的思考消息,可能已超时被移除")
|
||||
return
|
||||
|
||||
# 记录开始思考的时间,避免从思考到回复的时间太久
|
||||
|
||||
@@ -12,6 +12,7 @@ from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||
@dataclass
|
||||
class BotConfig:
|
||||
"""机器人配置类"""
|
||||
|
||||
INNER_VERSION: Version = None
|
||||
|
||||
BOT_QQ: Optional[int] = 1
|
||||
@@ -81,7 +82,7 @@ class BotConfig:
|
||||
PROMPT_PERSONALITY = [
|
||||
"曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧",
|
||||
"是一个女大学生,你有黑色头发,你会刷小红书",
|
||||
"是一个女大学生,你会刷b站,对ACG文化感兴趣"
|
||||
"是一个女大学生,你会刷b站,对ACG文化感兴趣",
|
||||
]
|
||||
|
||||
PROMPT_SCHEDULE_GEN = "一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书"
|
||||
@@ -90,14 +91,16 @@ class BotConfig:
|
||||
PERSONALITY_2: float = 0.3 # 第二种人格概率
|
||||
PERSONALITY_3: float = 0.1 # 第三种人格概率
|
||||
|
||||
memory_ban_words: list = field(default_factory=lambda: ['表情包', '图片', '回复', '聊天记录']) # 添加新的配置项默认值
|
||||
memory_ban_words: list = field(
|
||||
default_factory=lambda: ["表情包", "图片", "回复", "聊天记录"]
|
||||
) # 添加新的配置项默认值
|
||||
|
||||
@staticmethod
|
||||
def get_config_dir() -> str:
|
||||
"""获取配置文件目录"""
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
root_dir = os.path.abspath(os.path.join(current_dir, '..', '..', '..'))
|
||||
config_dir = os.path.join(root_dir, 'config')
|
||||
root_dir = os.path.abspath(os.path.join(current_dir, "..", "..", ".."))
|
||||
config_dir = os.path.join(root_dir, "config")
|
||||
if not os.path.exists(config_dir):
|
||||
os.makedirs(config_dir)
|
||||
return config_dir
|
||||
@@ -113,11 +116,8 @@ class BotConfig:
|
||||
|
||||
try:
|
||||
converted = SpecifierSet(value)
|
||||
except InvalidSpecifier as e:
|
||||
logger.error(
|
||||
f"{value} 分类使用了错误的版本约束表达式\n",
|
||||
"请阅读 https://semver.org/lang/zh-CN/ 修改代码"
|
||||
)
|
||||
except InvalidSpecifier:
|
||||
logger.error(f"{value} 分类使用了错误的版本约束表达式\n", "请阅读 https://semver.org/lang/zh-CN/ 修改代码")
|
||||
exit(1)
|
||||
|
||||
return converted
|
||||
@@ -131,12 +131,12 @@ class BotConfig:
|
||||
Version
|
||||
"""
|
||||
|
||||
if 'inner' in toml:
|
||||
if "inner" in toml:
|
||||
try:
|
||||
config_version: str = toml["inner"]["version"]
|
||||
except KeyError as e:
|
||||
logger.error(f"配置文件中 inner 段 不存在, 这是错误的配置文件")
|
||||
raise KeyError(f"配置文件中 inner 段 不存在 {e}, 这是错误的配置文件")
|
||||
logger.error("配置文件中 inner 段 不存在, 这是错误的配置文件")
|
||||
raise KeyError(f"配置文件中 inner 段 不存在 {e}, 这是错误的配置文件") from e
|
||||
else:
|
||||
toml["inner"] = {"version": "0.0.0"}
|
||||
config_version = toml["inner"]["version"]
|
||||
@@ -149,7 +149,7 @@ class BotConfig:
|
||||
"请阅读 https://semver.org/lang/zh-CN/ 修改配置,并参考本项目指定的模板进行修改\n"
|
||||
"本项目在不同的版本下有不同的模板,请注意识别"
|
||||
)
|
||||
raise InvalidVersion("配置文件中 inner段 的 version 键是错误的版本描述\n")
|
||||
raise InvalidVersion("配置文件中 inner段 的 version 键是错误的版本描述\n") from e
|
||||
|
||||
return ver
|
||||
|
||||
@@ -159,26 +159,26 @@ class BotConfig:
|
||||
config = cls()
|
||||
|
||||
def personality(parent: dict):
|
||||
personality_config = parent['personality']
|
||||
personality = personality_config.get('prompt_personality')
|
||||
personality_config = parent["personality"]
|
||||
personality = personality_config.get("prompt_personality")
|
||||
if len(personality) >= 2:
|
||||
logger.debug(f"载入自定义人格:{personality}")
|
||||
config.PROMPT_PERSONALITY = personality_config.get('prompt_personality', config.PROMPT_PERSONALITY)
|
||||
config.PROMPT_PERSONALITY = personality_config.get("prompt_personality", config.PROMPT_PERSONALITY)
|
||||
logger.info(f"载入自定义日程prompt:{personality_config.get('prompt_schedule', config.PROMPT_SCHEDULE_GEN)}")
|
||||
config.PROMPT_SCHEDULE_GEN = personality_config.get('prompt_schedule', config.PROMPT_SCHEDULE_GEN)
|
||||
config.PROMPT_SCHEDULE_GEN = personality_config.get("prompt_schedule", config.PROMPT_SCHEDULE_GEN)
|
||||
|
||||
if config.INNER_VERSION in SpecifierSet(">=0.0.2"):
|
||||
config.PERSONALITY_1 = personality_config.get('personality_1_probability', config.PERSONALITY_1)
|
||||
config.PERSONALITY_2 = personality_config.get('personality_2_probability', config.PERSONALITY_2)
|
||||
config.PERSONALITY_3 = personality_config.get('personality_3_probability', config.PERSONALITY_3)
|
||||
config.PERSONALITY_1 = personality_config.get("personality_1_probability", config.PERSONALITY_1)
|
||||
config.PERSONALITY_2 = personality_config.get("personality_2_probability", config.PERSONALITY_2)
|
||||
config.PERSONALITY_3 = personality_config.get("personality_3_probability", config.PERSONALITY_3)
|
||||
|
||||
def emoji(parent: dict):
|
||||
emoji_config = parent["emoji"]
|
||||
config.EMOJI_CHECK_INTERVAL = emoji_config.get("check_interval", config.EMOJI_CHECK_INTERVAL)
|
||||
config.EMOJI_REGISTER_INTERVAL = emoji_config.get("register_interval", config.EMOJI_REGISTER_INTERVAL)
|
||||
config.EMOJI_CHECK_PROMPT = emoji_config.get('check_prompt', config.EMOJI_CHECK_PROMPT)
|
||||
config.EMOJI_SAVE = emoji_config.get('auto_save', config.EMOJI_SAVE)
|
||||
config.EMOJI_CHECK = emoji_config.get('enable_check', config.EMOJI_CHECK)
|
||||
config.EMOJI_CHECK_PROMPT = emoji_config.get("check_prompt", config.EMOJI_CHECK_PROMPT)
|
||||
config.EMOJI_SAVE = emoji_config.get("auto_save", config.EMOJI_SAVE)
|
||||
config.EMOJI_CHECK = emoji_config.get("enable_check", config.EMOJI_CHECK)
|
||||
|
||||
def cq_code(parent: dict):
|
||||
cq_code_config = parent["cq_code"]
|
||||
@@ -195,8 +195,9 @@ class BotConfig:
|
||||
response_config = parent["response"]
|
||||
config.MODEL_R1_PROBABILITY = response_config.get("model_r1_probability", config.MODEL_R1_PROBABILITY)
|
||||
config.MODEL_V3_PROBABILITY = response_config.get("model_v3_probability", config.MODEL_V3_PROBABILITY)
|
||||
config.MODEL_R1_DISTILL_PROBABILITY = response_config.get("model_r1_distill_probability",
|
||||
config.MODEL_R1_DISTILL_PROBABILITY)
|
||||
config.MODEL_R1_DISTILL_PROBABILITY = response_config.get(
|
||||
"model_r1_distill_probability", config.MODEL_R1_DISTILL_PROBABILITY
|
||||
)
|
||||
config.max_response_length = response_config.get("max_response_length", config.max_response_length)
|
||||
|
||||
def model(parent: dict):
|
||||
@@ -213,7 +214,7 @@ class BotConfig:
|
||||
"llm_emotion_judge",
|
||||
"vlm",
|
||||
"embedding",
|
||||
"moderation"
|
||||
"moderation",
|
||||
]
|
||||
|
||||
for item in config_list:
|
||||
@@ -222,13 +223,7 @@ class BotConfig:
|
||||
|
||||
# base_url 的例子: SILICONFLOW_BASE_URL
|
||||
# key 的例子: SILICONFLOW_KEY
|
||||
cfg_target = {
|
||||
"name": "",
|
||||
"base_url": "",
|
||||
"key": "",
|
||||
"pri_in": 0,
|
||||
"pri_out": 0
|
||||
}
|
||||
cfg_target = {"name": "", "base_url": "", "key": "", "pri_in": 0, "pri_out": 0}
|
||||
|
||||
if config.INNER_VERSION in SpecifierSet("<=0.0.0"):
|
||||
cfg_target = cfg_item
|
||||
@@ -247,7 +242,7 @@ class BotConfig:
|
||||
cfg_target[i] = cfg_item[i]
|
||||
except KeyError as e:
|
||||
logger.error(f"{item} 中的必要字段不存在,请检查")
|
||||
raise KeyError(f"{item} 中的必要字段 {e} 不存在,请检查")
|
||||
raise KeyError(f"{item} 中的必要字段 {e} 不存在,请检查") from e
|
||||
|
||||
provider = cfg_item.get("provider")
|
||||
if provider is None:
|
||||
@@ -272,10 +267,12 @@ class BotConfig:
|
||||
|
||||
if config.INNER_VERSION in SpecifierSet(">=0.0.2"):
|
||||
config.thinking_timeout = msg_config.get("thinking_timeout", config.thinking_timeout)
|
||||
config.response_willing_amplifier = msg_config.get("response_willing_amplifier",
|
||||
config.response_willing_amplifier)
|
||||
config.response_interested_rate_amplifier = msg_config.get("response_interested_rate_amplifier",
|
||||
config.response_interested_rate_amplifier)
|
||||
config.response_willing_amplifier = msg_config.get(
|
||||
"response_willing_amplifier", config.response_willing_amplifier
|
||||
)
|
||||
config.response_interested_rate_amplifier = msg_config.get(
|
||||
"response_interested_rate_amplifier", config.response_interested_rate_amplifier
|
||||
)
|
||||
config.down_frequency_rate = msg_config.get("down_frequency_rate", config.down_frequency_rate)
|
||||
|
||||
def memory(parent: dict):
|
||||
@@ -303,10 +300,12 @@ class BotConfig:
|
||||
config.chinese_typo_enable = chinese_typo_config.get("enable", config.chinese_typo_enable)
|
||||
config.chinese_typo_error_rate = chinese_typo_config.get("error_rate", config.chinese_typo_error_rate)
|
||||
config.chinese_typo_min_freq = chinese_typo_config.get("min_freq", config.chinese_typo_min_freq)
|
||||
config.chinese_typo_tone_error_rate = chinese_typo_config.get("tone_error_rate",
|
||||
config.chinese_typo_tone_error_rate)
|
||||
config.chinese_typo_word_replace_rate = chinese_typo_config.get("word_replace_rate",
|
||||
config.chinese_typo_word_replace_rate)
|
||||
config.chinese_typo_tone_error_rate = chinese_typo_config.get(
|
||||
"tone_error_rate", config.chinese_typo_tone_error_rate
|
||||
)
|
||||
config.chinese_typo_word_replace_rate = chinese_typo_config.get(
|
||||
"word_replace_rate", config.chinese_typo_word_replace_rate
|
||||
)
|
||||
|
||||
def groups(parent: dict):
|
||||
groups_config = parent["groups"]
|
||||
@@ -325,61 +324,19 @@ class BotConfig:
|
||||
# 例如:"notice": "personality 将在 1.3.2 后被移除",那么在有效版本中的用户就会虽然可以
|
||||
# 正常执行程序,但是会看到这条自定义提示
|
||||
include_configs = {
|
||||
"personality": {
|
||||
"func": personality,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"emoji": {
|
||||
"func": emoji,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"cq_code": {
|
||||
"func": cq_code,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"bot": {
|
||||
"func": bot,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"response": {
|
||||
"func": response,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"model": {
|
||||
"func": model,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"message": {
|
||||
"func": message,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"memory": {
|
||||
"func": memory,
|
||||
"support": ">=0.0.0",
|
||||
"necessary": False
|
||||
},
|
||||
"mood": {
|
||||
"func": mood,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"keywords_reaction": {
|
||||
"func": keywords_reaction,
|
||||
"support": ">=0.0.2",
|
||||
"necessary": False
|
||||
},
|
||||
"chinese_typo": {
|
||||
"func": chinese_typo,
|
||||
"support": ">=0.0.3",
|
||||
"necessary": False
|
||||
},
|
||||
"groups": {
|
||||
"func": groups,
|
||||
"support": ">=0.0.0"
|
||||
},
|
||||
"others": {
|
||||
"func": others,
|
||||
"support": ">=0.0.0"
|
||||
}
|
||||
"personality": {"func": personality, "support": ">=0.0.0"},
|
||||
"emoji": {"func": emoji, "support": ">=0.0.0"},
|
||||
"cq_code": {"func": cq_code, "support": ">=0.0.0"},
|
||||
"bot": {"func": bot, "support": ">=0.0.0"},
|
||||
"response": {"func": response, "support": ">=0.0.0"},
|
||||
"model": {"func": model, "support": ">=0.0.0"},
|
||||
"message": {"func": message, "support": ">=0.0.0"},
|
||||
"memory": {"func": memory, "support": ">=0.0.0", "necessary": False},
|
||||
"mood": {"func": mood, "support": ">=0.0.0"},
|
||||
"keywords_reaction": {"func": keywords_reaction, "support": ">=0.0.2", "necessary": False},
|
||||
"chinese_typo": {"func": chinese_typo, "support": ">=0.0.3", "necessary": False},
|
||||
"groups": {"func": groups, "support": ">=0.0.0"},
|
||||
"others": {"func": others, "support": ">=0.0.0"},
|
||||
}
|
||||
|
||||
# 原地修改,将 字符串版本表达式 转换成 版本对象
|
||||
@@ -391,7 +348,7 @@ class BotConfig:
|
||||
with open(config_path, "rb") as f:
|
||||
try:
|
||||
toml_dict = tomli.load(f)
|
||||
except(tomli.TOMLDecodeError) as e:
|
||||
except tomli.TOMLDecodeError as e:
|
||||
logger.critical(f"配置文件bot_config.toml填写有误,请检查第{e.lineno}行第{e.colno}处:{e.msg}")
|
||||
exit(1)
|
||||
|
||||
@@ -406,7 +363,7 @@ class BotConfig:
|
||||
# 检查配置文件版本是否在支持范围内
|
||||
if config.INNER_VERSION in group_specifierset:
|
||||
# 如果版本在支持范围内,检查是否存在通知
|
||||
if 'notice' in include_configs[key]:
|
||||
if "notice" in include_configs[key]:
|
||||
logger.warning(include_configs[key]["notice"])
|
||||
|
||||
include_configs[key]["func"](toml_dict)
|
||||
@@ -420,7 +377,7 @@ class BotConfig:
|
||||
raise InvalidVersion(f"当前程序仅支持以下版本范围: {group_specifierset}")
|
||||
|
||||
# 如果 necessary 项目存在,而且显式声明是 False,进入特殊处理
|
||||
elif "necessary" in include_configs[key] and include_configs[key].get("necessary") == False:
|
||||
elif "necessary" in include_configs[key] and include_configs[key].get("necessary") is False:
|
||||
# 通过 pass 处理的项虽然直接忽略也是可以的,但是为了不增加理解困难,依然需要在这里显式处理
|
||||
if key == "keywords_reaction":
|
||||
pass
|
||||
|
||||
@@ -155,8 +155,8 @@ class CQCode:
|
||||
logger.error(f"最终请求失败: {str(e)}")
|
||||
time.sleep(1.5 ** retry) # 指数退避
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"[未知错误]")
|
||||
except Exception:
|
||||
logger.exception("[未知错误]")
|
||||
return None
|
||||
|
||||
return None
|
||||
@@ -281,7 +281,7 @@ class CQCode:
|
||||
logger.debug(f"合并后的转发消息: {combined_messages}")
|
||||
return f"[转发消息:\n{combined_messages}]"
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.exception("处理转发消息失败")
|
||||
return '[转发消息]'
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ class EmojiManager:
|
||||
self._initialized = True
|
||||
# 启动时执行一次完整性检查
|
||||
self.check_emoji_file_integrity()
|
||||
except Exception as e:
|
||||
logger.exception(f"初始化表情管理器失败")
|
||||
except Exception:
|
||||
logger.exception("初始化表情管理器失败")
|
||||
|
||||
def _ensure_db(self):
|
||||
"""确保数据库已初始化"""
|
||||
@@ -87,8 +87,8 @@ class EmojiManager:
|
||||
{'_id': emoji_id},
|
||||
{'$inc': {'usage_count': 1}}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(f"记录表情使用失败")
|
||||
except Exception:
|
||||
logger.exception("记录表情使用失败")
|
||||
|
||||
async def get_emoji_for_text(self, text: str) -> Optional[str]:
|
||||
"""根据文本内容获取相关表情包
|
||||
@@ -203,7 +203,7 @@ class EmojiManager:
|
||||
try:
|
||||
prompt = f'这是{global_config.BOT_NICKNAME}将要发送的消息内容:\n{text}\n若要为其配上表情包,请你输出这个表情包应该表达怎样的情感,应该给人什么样的感觉,不要太简洁也不要太长,注意不要输出任何对消息内容的分析内容,只输出\"一种什么样的感觉\"中间的形容词部分。'
|
||||
|
||||
content, _ = await self.llm_emotion_judge.generate_response_async(prompt)
|
||||
content, _ = await self.llm_emotion_judge.generate_response_async(prompt,temperature=1.5)
|
||||
logger.info(f"输出描述: {content}")
|
||||
return content
|
||||
|
||||
@@ -264,8 +264,8 @@ class EmojiManager:
|
||||
else:
|
||||
logger.warning(f"跳过表情包: {filename}")
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"扫描表情包失败")
|
||||
except Exception:
|
||||
logger.exception("扫描表情包失败")
|
||||
|
||||
async def _periodic_scan(self, interval_MINS: int = 10):
|
||||
"""定期扫描新表情包"""
|
||||
|
||||
@@ -94,7 +94,7 @@ class ResponseGenerator:
|
||||
try:
|
||||
content, reasoning_content = await model.generate_response(prompt)
|
||||
except Exception:
|
||||
logger.exception(f"生成回复时出错")
|
||||
logger.exception("生成回复时出错")
|
||||
return None
|
||||
|
||||
# 保存到数据库
|
||||
@@ -146,7 +146,7 @@ class ResponseGenerator:
|
||||
return ["neutral"]
|
||||
|
||||
except Exception:
|
||||
logger.exception(f"获取情感标签时出错")
|
||||
logger.exception("获取情感标签时出错")
|
||||
return ["neutral"]
|
||||
|
||||
async def _process_response(self, content: str) -> Tuple[List[str], List[str]]:
|
||||
|
||||
@@ -61,7 +61,7 @@ class Message_Sender:
|
||||
auto_escape=auto_escape
|
||||
)
|
||||
logger.debug(f"发送消息{message}成功")
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.exception(f"发送消息{message}失败")
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class MessageContainer:
|
||||
return True
|
||||
return False
|
||||
except Exception:
|
||||
logger.exception(f"移除消息时发生错误")
|
||||
logger.exception("移除消息时发生错误")
|
||||
return False
|
||||
|
||||
def has_messages(self) -> bool:
|
||||
@@ -214,7 +214,7 @@ class MessageManager:
|
||||
if not container.remove_message(msg):
|
||||
logger.warning("尝试删除不存在的消息")
|
||||
except Exception:
|
||||
logger.exception(f"处理超时消息时发生错误")
|
||||
logger.exception("处理超时消息时发生错误")
|
||||
continue
|
||||
|
||||
async def start_processor(self):
|
||||
|
||||
@@ -45,6 +45,6 @@ class MessageStorage:
|
||||
|
||||
self.db.db.messages.insert_one(message_data)
|
||||
except Exception:
|
||||
logger.exception(f"存储消息失败")
|
||||
logger.exception("存储消息失败")
|
||||
|
||||
# 如果需要其他存储相关的函数,可以在这里添加
|
||||
|
||||
@@ -79,7 +79,7 @@ class KnowledgeLibrary:
|
||||
content = f.read()
|
||||
|
||||
# 按1024字符分段
|
||||
segments = [content[i:i+600] for i in range(0, len(content), 600)]
|
||||
segments = [content[i:i+600] for i in range(0, len(content), 300)]
|
||||
|
||||
# 处理每个分段
|
||||
for segment in segments:
|
||||
|
||||
@@ -25,26 +25,46 @@ class Memory_graph:
|
||||
self.db = Database.get_instance()
|
||||
|
||||
def connect_dot(self, concept1, concept2):
|
||||
# 如果边已存在,增加 strength
|
||||
# 避免自连接
|
||||
if concept1 == concept2:
|
||||
return
|
||||
|
||||
current_time = datetime.datetime.now().timestamp()
|
||||
|
||||
# 如果边已存在,增加 strength
|
||||
if self.G.has_edge(concept1, concept2):
|
||||
self.G[concept1][concept2]['strength'] = self.G[concept1][concept2].get('strength', 1) + 1
|
||||
# 更新最后修改时间
|
||||
self.G[concept1][concept2]['last_modified'] = current_time
|
||||
else:
|
||||
# 如果是新边,初始化 strength 为 1
|
||||
self.G.add_edge(concept1, concept2, strength=1)
|
||||
# 如果是新边,初始化 strength 为 1
|
||||
self.G.add_edge(concept1, concept2,
|
||||
strength=1,
|
||||
created_time=current_time, # 添加创建时间
|
||||
last_modified=current_time) # 添加最后修改时间
|
||||
|
||||
def add_dot(self, concept, memory):
|
||||
current_time = datetime.datetime.now().timestamp()
|
||||
|
||||
if concept in self.G:
|
||||
# 如果节点已存在,将新记忆添加到现有列表中
|
||||
if 'memory_items' in self.G.nodes[concept]:
|
||||
if not isinstance(self.G.nodes[concept]['memory_items'], list):
|
||||
# 如果当前不是列表,将其转换为列表
|
||||
self.G.nodes[concept]['memory_items'] = [self.G.nodes[concept]['memory_items']]
|
||||
self.G.nodes[concept]['memory_items'].append(memory)
|
||||
# 更新最后修改时间
|
||||
self.G.nodes[concept]['last_modified'] = current_time
|
||||
else:
|
||||
self.G.nodes[concept]['memory_items'] = [memory]
|
||||
# 如果节点存在但没有memory_items,说明是第一次添加memory,设置created_time
|
||||
if 'created_time' not in self.G.nodes[concept]:
|
||||
self.G.nodes[concept]['created_time'] = current_time
|
||||
self.G.nodes[concept]['last_modified'] = current_time
|
||||
else:
|
||||
# 如果是新节点,创建新的记忆列表
|
||||
self.G.add_node(concept, memory_items=[memory])
|
||||
# 如果是新节点,创建新的记忆列表
|
||||
self.G.add_node(concept,
|
||||
memory_items=[memory],
|
||||
created_time=current_time, # 添加创建时间
|
||||
last_modified=current_time) # 添加最后修改时间
|
||||
|
||||
def get_dot(self, concept):
|
||||
# 检查节点是否存在于图中
|
||||
@@ -191,15 +211,11 @@ class Hippocampus:
|
||||
async def memory_compress(self, messages: list, compress_rate=0.1):
|
||||
"""压缩消息记录为记忆
|
||||
|
||||
Args:
|
||||
messages: 消息记录字典列表,每个字典包含text和time字段
|
||||
compress_rate: 压缩率
|
||||
|
||||
Returns:
|
||||
set: (话题, 记忆) 元组集合
|
||||
tuple: (压缩记忆集合, 相似主题字典)
|
||||
"""
|
||||
if not messages:
|
||||
return set()
|
||||
return set(), {}
|
||||
|
||||
# 合并消息文本,同时保留时间信息
|
||||
input_text = ""
|
||||
@@ -246,12 +262,33 @@ class Hippocampus:
|
||||
|
||||
# 等待所有任务完成
|
||||
compressed_memory = set()
|
||||
similar_topics_dict = {} # 存储每个话题的相似主题列表
|
||||
for topic, task in tasks:
|
||||
response = await task
|
||||
if response:
|
||||
compressed_memory.add((topic, response[0]))
|
||||
# 为每个话题查找相似的已存在主题
|
||||
existing_topics = list(self.memory_graph.G.nodes())
|
||||
similar_topics = []
|
||||
|
||||
return compressed_memory
|
||||
for existing_topic in existing_topics:
|
||||
topic_words = set(jieba.cut(topic))
|
||||
existing_words = set(jieba.cut(existing_topic))
|
||||
|
||||
all_words = topic_words | existing_words
|
||||
v1 = [1 if word in topic_words else 0 for word in all_words]
|
||||
v2 = [1 if word in existing_words else 0 for word in all_words]
|
||||
|
||||
similarity = cosine_similarity(v1, v2)
|
||||
|
||||
if similarity >= 0.6:
|
||||
similar_topics.append((existing_topic, similarity))
|
||||
|
||||
similar_topics.sort(key=lambda x: x[1], reverse=True)
|
||||
similar_topics = similar_topics[:5]
|
||||
similar_topics_dict[topic] = similar_topics
|
||||
|
||||
return compressed_memory, similar_topics_dict
|
||||
|
||||
def calculate_topic_num(self, text, compress_rate):
|
||||
"""计算文本的话题数量"""
|
||||
@@ -265,33 +302,40 @@ class Hippocampus:
|
||||
return topic_num
|
||||
|
||||
async def operation_build_memory(self, chat_size=20):
|
||||
# 最近消息获取频率
|
||||
time_frequency = {'near': 2, 'mid': 4, 'far': 2}
|
||||
memory_sample = self.get_memory_sample(chat_size, time_frequency)
|
||||
time_frequency = {'near': 3, 'mid': 8, 'far': 5}
|
||||
memory_samples = self.get_memory_sample(chat_size, time_frequency)
|
||||
|
||||
for i, input_text in enumerate(memory_sample, 1):
|
||||
# 加载进度可视化
|
||||
for i, messages in enumerate(memory_samples, 1):
|
||||
all_topics = []
|
||||
progress = (i / len(memory_sample)) * 100
|
||||
# 加载进度可视化
|
||||
progress = (i / len(memory_samples)) * 100
|
||||
bar_length = 30
|
||||
filled_length = int(bar_length * i // len(memory_sample))
|
||||
filled_length = int(bar_length * i // len(memory_samples))
|
||||
bar = '█' * filled_length + '-' * (bar_length - filled_length)
|
||||
logger.debug(f"进度: [{bar}] {progress:.1f}% ({i}/{len(memory_sample)})")
|
||||
logger.debug(f"进度: [{bar}] {progress:.1f}% ({i}/{len(memory_samples)})")
|
||||
|
||||
# 生成压缩后记忆 ,表现为 (话题,记忆) 的元组
|
||||
compressed_memory = set()
|
||||
compress_rate = 0.1
|
||||
compressed_memory = await self.memory_compress(input_text, compress_rate)
|
||||
logger.info(f"压缩后记忆数量: {len(compressed_memory)}")
|
||||
compressed_memory, similar_topics_dict = await self.memory_compress(messages, compress_rate)
|
||||
logger.info(f"压缩后记忆数量: {len(compressed_memory)},似曾相识的话题: {len(similar_topics_dict)}")
|
||||
|
||||
# 将记忆加入到图谱中
|
||||
for topic, memory in compressed_memory:
|
||||
logger.info(f"添加节点: {topic}")
|
||||
self.memory_graph.add_dot(topic, memory)
|
||||
all_topics.append(topic) # 收集所有话题
|
||||
all_topics.append(topic)
|
||||
|
||||
# 连接相似的已存在主题
|
||||
if topic in similar_topics_dict:
|
||||
similar_topics = similar_topics_dict[topic]
|
||||
for similar_topic, similarity in similar_topics:
|
||||
if topic != similar_topic:
|
||||
strength = int(similarity * 10)
|
||||
logger.info(f"连接相似节点: {topic} 和 {similar_topic} (强度: {strength})")
|
||||
self.memory_graph.G.add_edge(topic, similar_topic, strength=strength)
|
||||
|
||||
# 连接同批次的相关话题
|
||||
for i in range(len(all_topics)):
|
||||
for j in range(i + 1, len(all_topics)):
|
||||
logger.info(f"连接节点: {all_topics[i]} 和 {all_topics[j]}")
|
||||
logger.info(f"连接同批次节点: {all_topics[i]} 和 {all_topics[j]}")
|
||||
self.memory_graph.connect_dot(all_topics[i], all_topics[j])
|
||||
|
||||
self.sync_memory_to_db()
|
||||
@@ -302,7 +346,7 @@ class Hippocampus:
|
||||
db_nodes = list(self.memory_graph.db.db.graph_data.nodes.find())
|
||||
memory_nodes = list(self.memory_graph.G.nodes(data=True))
|
||||
|
||||
# 转换数据库节点为字典格式,方便查找
|
||||
# 转换数据库节点为字典格式,方便查找
|
||||
db_nodes_dict = {node['concept']: node for node in db_nodes}
|
||||
|
||||
# 检查并更新节点
|
||||
@@ -314,12 +358,18 @@ class Hippocampus:
|
||||
# 计算内存中节点的特征值
|
||||
memory_hash = self.calculate_node_hash(concept, memory_items)
|
||||
|
||||
# 获取时间信息
|
||||
created_time = data.get('created_time', datetime.datetime.now().timestamp())
|
||||
last_modified = data.get('last_modified', datetime.datetime.now().timestamp())
|
||||
|
||||
if concept not in db_nodes_dict:
|
||||
# 数据库中缺少的节点,添加
|
||||
# 数据库中缺少的节点,添加
|
||||
node_data = {
|
||||
'concept': concept,
|
||||
'memory_items': memory_items,
|
||||
'hash': memory_hash
|
||||
'hash': memory_hash,
|
||||
'created_time': created_time,
|
||||
'last_modified': last_modified
|
||||
}
|
||||
self.memory_graph.db.db.graph_data.nodes.insert_one(node_data)
|
||||
else:
|
||||
@@ -327,25 +377,21 @@ class Hippocampus:
|
||||
db_node = db_nodes_dict[concept]
|
||||
db_hash = db_node.get('hash', None)
|
||||
|
||||
# 如果特征值不同,则更新节点
|
||||
# 如果特征值不同,则更新节点
|
||||
if db_hash != memory_hash:
|
||||
self.memory_graph.db.db.graph_data.nodes.update_one(
|
||||
{'concept': concept},
|
||||
{'$set': {
|
||||
'memory_items': memory_items,
|
||||
'hash': memory_hash
|
||||
'hash': memory_hash,
|
||||
'created_time': created_time,
|
||||
'last_modified': last_modified
|
||||
}}
|
||||
)
|
||||
|
||||
# 检查并删除数据库中多余的节点
|
||||
memory_concepts = set(node[0] for node in memory_nodes)
|
||||
for db_node in db_nodes:
|
||||
if db_node['concept'] not in memory_concepts:
|
||||
self.memory_graph.db.db.graph_data.nodes.delete_one({'concept': db_node['concept']})
|
||||
|
||||
# 处理边的信息
|
||||
db_edges = list(self.memory_graph.db.db.graph_data.edges.find())
|
||||
memory_edges = list(self.memory_graph.G.edges())
|
||||
memory_edges = list(self.memory_graph.G.edges(data=True))
|
||||
|
||||
# 创建边的哈希值字典
|
||||
db_edge_dict = {}
|
||||
@@ -357,10 +403,14 @@ class Hippocampus:
|
||||
}
|
||||
|
||||
# 检查并更新边
|
||||
for source, target in memory_edges:
|
||||
for source, target, data in memory_edges:
|
||||
edge_hash = self.calculate_edge_hash(source, target)
|
||||
edge_key = (source, target)
|
||||
strength = self.memory_graph.G[source][target].get('strength', 1)
|
||||
strength = data.get('strength', 1)
|
||||
|
||||
# 获取边的时间信息
|
||||
created_time = data.get('created_time', datetime.datetime.now().timestamp())
|
||||
last_modified = data.get('last_modified', datetime.datetime.now().timestamp())
|
||||
|
||||
if edge_key not in db_edge_dict:
|
||||
# 添加新边
|
||||
@@ -368,7 +418,9 @@ class Hippocampus:
|
||||
'source': source,
|
||||
'target': target,
|
||||
'strength': strength,
|
||||
'hash': edge_hash
|
||||
'hash': edge_hash,
|
||||
'created_time': created_time,
|
||||
'last_modified': last_modified
|
||||
}
|
||||
self.memory_graph.db.db.graph_data.edges.insert_one(edge_data)
|
||||
else:
|
||||
@@ -378,20 +430,12 @@ class Hippocampus:
|
||||
{'source': source, 'target': target},
|
||||
{'$set': {
|
||||
'hash': edge_hash,
|
||||
'strength': strength
|
||||
'strength': strength,
|
||||
'created_time': created_time,
|
||||
'last_modified': last_modified
|
||||
}}
|
||||
)
|
||||
|
||||
# 删除多余的边
|
||||
memory_edge_set = set(memory_edges)
|
||||
for edge_key in db_edge_dict:
|
||||
if edge_key not in memory_edge_set:
|
||||
source, target = edge_key
|
||||
self.memory_graph.db.db.graph_data.edges.delete_one({
|
||||
'source': source,
|
||||
'target': target
|
||||
})
|
||||
|
||||
def sync_memory_from_db(self):
|
||||
"""从数据库同步数据到内存中的图结构"""
|
||||
# 清空当前图
|
||||
@@ -405,61 +449,107 @@ class Hippocampus:
|
||||
# 确保memory_items是列表
|
||||
if not isinstance(memory_items, list):
|
||||
memory_items = [memory_items] if memory_items else []
|
||||
|
||||
# 获取时间信息
|
||||
created_time = node.get('created_time', datetime.datetime.now().timestamp())
|
||||
last_modified = node.get('last_modified', datetime.datetime.now().timestamp())
|
||||
|
||||
# 添加节点到图中
|
||||
self.memory_graph.G.add_node(concept, memory_items=memory_items)
|
||||
self.memory_graph.G.add_node(concept,
|
||||
memory_items=memory_items,
|
||||
created_time=created_time,
|
||||
last_modified=last_modified)
|
||||
|
||||
# 从数据库加载所有边
|
||||
edges = self.memory_graph.db.db.graph_data.edges.find()
|
||||
for edge in edges:
|
||||
source = edge['source']
|
||||
target = edge['target']
|
||||
strength = edge.get('strength', 1) # 获取 strength,默认为 1
|
||||
strength = edge.get('strength', 1) # 获取 strength,默认为 1
|
||||
|
||||
# 获取时间信息
|
||||
created_time = edge.get('created_time', datetime.datetime.now().timestamp())
|
||||
last_modified = edge.get('last_modified', datetime.datetime.now().timestamp())
|
||||
|
||||
# 只有当源节点和目标节点都存在时才添加边
|
||||
if source in self.memory_graph.G and target in self.memory_graph.G:
|
||||
self.memory_graph.G.add_edge(source, target, strength=strength)
|
||||
self.memory_graph.G.add_edge(source, target,
|
||||
strength=strength,
|
||||
created_time=created_time,
|
||||
last_modified=last_modified)
|
||||
|
||||
async def operation_forget_topic(self, percentage=0.1):
|
||||
"""随机选择图中一定比例的节点进行检查,根据条件决定是否遗忘"""
|
||||
# 获取所有节点
|
||||
"""随机选择图中一定比例的节点和边进行检查,根据时间条件决定是否遗忘"""
|
||||
all_nodes = list(self.memory_graph.G.nodes())
|
||||
# 计算要检查的节点数量
|
||||
check_count = max(1, int(len(all_nodes) * percentage))
|
||||
# 随机选择节点
|
||||
nodes_to_check = random.sample(all_nodes, check_count)
|
||||
all_edges = list(self.memory_graph.G.edges())
|
||||
|
||||
forgotten_nodes = []
|
||||
check_nodes_count = max(1, int(len(all_nodes) * percentage))
|
||||
check_edges_count = max(1, int(len(all_edges) * percentage))
|
||||
|
||||
nodes_to_check = random.sample(all_nodes, check_nodes_count)
|
||||
edges_to_check = random.sample(all_edges, check_edges_count)
|
||||
|
||||
edge_changes = {'weakened': 0, 'removed': 0}
|
||||
node_changes = {'reduced': 0, 'removed': 0}
|
||||
|
||||
current_time = datetime.datetime.now().timestamp()
|
||||
|
||||
# 检查并遗忘连接
|
||||
logger.info("开始检查连接...")
|
||||
for source, target in edges_to_check:
|
||||
edge_data = self.memory_graph.G[source][target]
|
||||
last_modified = edge_data.get('last_modified')
|
||||
# print(source,target)
|
||||
# print(f"float(last_modified):{float(last_modified)}" )
|
||||
# print(f"current_time:{current_time}")
|
||||
# print(f"current_time - last_modified:{current_time - last_modified}")
|
||||
if current_time - last_modified > 3600*24: # test
|
||||
current_strength = edge_data.get('strength', 1)
|
||||
new_strength = current_strength - 1
|
||||
|
||||
if new_strength <= 0:
|
||||
self.memory_graph.G.remove_edge(source, target)
|
||||
edge_changes['removed'] += 1
|
||||
logger.info(f"\033[1;31m[连接移除]\033[0m {source} - {target}")
|
||||
else:
|
||||
edge_data['strength'] = new_strength
|
||||
edge_data['last_modified'] = current_time
|
||||
edge_changes['weakened'] += 1
|
||||
logger.info(f"\033[1;34m[连接减弱]\033[0m {source} - {target} (强度: {current_strength} -> {new_strength})")
|
||||
|
||||
# 检查并遗忘话题
|
||||
logger.info("开始检查节点...")
|
||||
for node in nodes_to_check:
|
||||
# 获取节点的连接数
|
||||
connections = self.memory_graph.G.degree(node)
|
||||
node_data = self.memory_graph.G.nodes[node]
|
||||
last_modified = node_data.get('last_modified', current_time)
|
||||
|
||||
# 获取节点的内容条数
|
||||
memory_items = self.memory_graph.G.nodes[node].get('memory_items', [])
|
||||
if current_time - last_modified > 3600*24: # test
|
||||
memory_items = node_data.get('memory_items', [])
|
||||
if not isinstance(memory_items, list):
|
||||
memory_items = [memory_items] if memory_items else []
|
||||
content_count = len(memory_items)
|
||||
|
||||
# 检查连接强度
|
||||
weak_connections = True
|
||||
if connections > 1: # 只有当连接数大于1时才检查强度
|
||||
for neighbor in self.memory_graph.G.neighbors(node):
|
||||
strength = self.memory_graph.G[node][neighbor].get('strength', 1)
|
||||
if strength > 2:
|
||||
weak_connections = False
|
||||
break
|
||||
if memory_items:
|
||||
current_count = len(memory_items)
|
||||
removed_item = random.choice(memory_items)
|
||||
memory_items.remove(removed_item)
|
||||
|
||||
# 如果满足遗忘条件
|
||||
if (connections <= 1 and weak_connections) or content_count <= 2:
|
||||
removed_item = self.memory_graph.forget_topic(node)
|
||||
if removed_item:
|
||||
forgotten_nodes.append((node, removed_item))
|
||||
logger.debug(f"遗忘节点 {node} 的记忆: {removed_item}")
|
||||
|
||||
# 同步到数据库
|
||||
if forgotten_nodes:
|
||||
self.sync_memory_to_db()
|
||||
logger.debug(f"完成遗忘操作,共遗忘 {len(forgotten_nodes)} 个节点的记忆")
|
||||
if memory_items:
|
||||
self.memory_graph.G.nodes[node]['memory_items'] = memory_items
|
||||
self.memory_graph.G.nodes[node]['last_modified'] = current_time
|
||||
node_changes['reduced'] += 1
|
||||
logger.info(f"\033[1;33m[记忆减少]\033[0m {node} (记忆数量: {current_count} -> {len(memory_items)})")
|
||||
else:
|
||||
logger.debug("本次检查没有节点满足遗忘条件")
|
||||
self.memory_graph.G.remove_node(node)
|
||||
node_changes['removed'] += 1
|
||||
logger.info(f"\033[1;31m[节点移除]\033[0m {node}")
|
||||
|
||||
if any(count > 0 for count in edge_changes.values()) or any(count > 0 for count in node_changes.values()):
|
||||
self.sync_memory_to_db()
|
||||
logger.info("\n遗忘操作统计:")
|
||||
logger.info(f"连接变化: {edge_changes['weakened']} 个减弱, {edge_changes['removed']} 个移除")
|
||||
logger.info(f"节点变化: {node_changes['reduced']} 个减少记忆, {node_changes['removed']} 个移除")
|
||||
else:
|
||||
logger.info("\n本次检查没有节点或连接满足遗忘条件")
|
||||
|
||||
async def merge_memory(self, topic):
|
||||
"""
|
||||
@@ -486,7 +576,7 @@ class Hippocampus:
|
||||
logger.debug(f"选择的记忆:\n{merged_text}")
|
||||
|
||||
# 使用memory_compress生成新的压缩记忆
|
||||
compressed_memories = await self.memory_compress(selected_memories, 0.1)
|
||||
compressed_memories, _ = await self.memory_compress(selected_memories, 0.1)
|
||||
|
||||
# 从原记忆列表中移除被选中的记忆
|
||||
for memory in selected_memories:
|
||||
|
||||
1208
src/plugins/memory_system/memory_test1.py
Normal file
1208
src/plugins/memory_system/memory_test1.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -44,8 +44,8 @@ class LLM_request:
|
||||
self.db.db.llm_usage.create_index([("model_name", 1)])
|
||||
self.db.db.llm_usage.create_index([("user_id", 1)])
|
||||
self.db.db.llm_usage.create_index([("request_type", 1)])
|
||||
except Exception as e:
|
||||
logger.error(f"创建数据库索引失败")
|
||||
except Exception:
|
||||
logger.error("创建数据库索引失败")
|
||||
|
||||
def _record_usage(self, prompt_tokens: int, completion_tokens: int, total_tokens: int,
|
||||
user_id: str = "system", request_type: str = "chat",
|
||||
@@ -80,7 +80,7 @@ class LLM_request:
|
||||
f"总计: {total_tokens}"
|
||||
)
|
||||
except Exception:
|
||||
logger.error(f"记录token使用情况失败")
|
||||
logger.error("记录token使用情况失败")
|
||||
|
||||
def _calculate_cost(self, prompt_tokens: int, completion_tokens: int) -> float:
|
||||
"""计算API调用成本
|
||||
@@ -194,7 +194,7 @@ class LLM_request:
|
||||
if hasattr(global_config, 'llm_normal') and global_config.llm_normal.get(
|
||||
'name') == old_model_name:
|
||||
global_config.llm_normal['name'] = self.model_name
|
||||
logger.warning(f"已将全局配置中的 llm_normal 模型降级")
|
||||
logger.warning("已将全局配置中的 llm_normal 模型降级")
|
||||
|
||||
# 更新payload中的模型名
|
||||
if payload and 'model' in payload:
|
||||
@@ -227,7 +227,7 @@ class LLM_request:
|
||||
delta_content = ""
|
||||
accumulated_content += delta_content
|
||||
except Exception:
|
||||
logger.exception(f"解析流式输出错")
|
||||
logger.exception("解析流式输出错")
|
||||
content = accumulated_content
|
||||
reasoning_content = ""
|
||||
think_match = re.search(r'<think>(.*?)</think>', content, re.DOTALL)
|
||||
@@ -355,7 +355,7 @@ class LLM_request:
|
||||
"""构建请求头"""
|
||||
if no_key:
|
||||
return {
|
||||
"Authorization": f"Bearer **********",
|
||||
"Authorization": "Bearer **********",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
else:
|
||||
|
||||
@@ -91,7 +91,7 @@ class ScheduleGenerator:
|
||||
try:
|
||||
schedule_dict = json.loads(schedule_text)
|
||||
return schedule_dict
|
||||
except json.JSONDecodeError as e:
|
||||
except json.JSONDecodeError:
|
||||
logger.exception("解析日程失败: {}".format(schedule_text))
|
||||
return False
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ class LLMStatistics:
|
||||
all_stats = self._collect_all_statistics()
|
||||
self._save_statistics(all_stats)
|
||||
except Exception:
|
||||
logger.exception(f"统计数据处理失败")
|
||||
logger.exception("统计数据处理失败")
|
||||
|
||||
# 等待1分钟
|
||||
for _ in range(60):
|
||||
|
||||
Reference in New Issue
Block a user