Merge remote-tracking branch 'upstream/debug' into tc_refractor
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
import asyncio
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
|
||||
from loguru import logger
|
||||
from nonebot import get_driver, on_command, on_message, require
|
||||
from nonebot import get_driver, on_message, require
|
||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageSegment
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
|
||||
from ...common.database import Database
|
||||
@@ -19,6 +16,10 @@ from .emoji_manager import emoji_manager
|
||||
from .relationship_manager import relationship_manager
|
||||
from .willing_manager import willing_manager
|
||||
from .chat_stream import chat_manager
|
||||
from ..memory_system.memory import hippocampus, memory_graph
|
||||
from .bot import ChatBot
|
||||
from .message_sender import message_manager, message_sender
|
||||
|
||||
|
||||
# 创建LLM统计实例
|
||||
llm_stats = LLMStatistics("llm_statistics.txt")
|
||||
@@ -31,27 +32,20 @@ driver = get_driver()
|
||||
config = driver.config
|
||||
|
||||
Database.initialize(
|
||||
host= config.MONGODB_HOST,
|
||||
port= int(config.MONGODB_PORT),
|
||||
db_name= config.DATABASE_NAME,
|
||||
username= config.MONGODB_USERNAME,
|
||||
password= config.MONGODB_PASSWORD,
|
||||
auth_source= config.MONGODB_AUTH_SOURCE
|
||||
host=config.MONGODB_HOST,
|
||||
port=int(config.MONGODB_PORT),
|
||||
db_name=config.DATABASE_NAME,
|
||||
username=config.MONGODB_USERNAME,
|
||||
password=config.MONGODB_PASSWORD,
|
||||
auth_source=config.MONGODB_AUTH_SOURCE
|
||||
)
|
||||
print("\033[1;32m[初始化数据库完成]\033[0m")
|
||||
logger.success("初始化数据库成功")
|
||||
|
||||
|
||||
# 导入其他模块
|
||||
from ..memory_system.memory import hippocampus, memory_graph
|
||||
from .bot import ChatBot
|
||||
|
||||
# from .message_send_control import message_sender
|
||||
from .message_sender import message_manager, message_sender
|
||||
|
||||
# 初始化表情管理器
|
||||
emoji_manager.initialize()
|
||||
|
||||
print(f"\033[1;32m正在唤醒{global_config.BOT_NICKNAME}......\033[0m")
|
||||
logger.debug(f"正在唤醒{global_config.BOT_NICKNAME}......")
|
||||
# 创建机器人实例
|
||||
chat_bot = ChatBot()
|
||||
# 注册群消息处理器
|
||||
@@ -60,71 +54,80 @@ group_msg = on_message(priority=5)
|
||||
scheduler = require("nonebot_plugin_apscheduler").scheduler
|
||||
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def start_background_tasks():
|
||||
"""启动后台任务"""
|
||||
# 启动LLM统计
|
||||
llm_stats.start()
|
||||
print("\033[1;32m[初始化]\033[0m LLM统计功能已启动")
|
||||
|
||||
logger.success("LLM统计功能启动成功")
|
||||
|
||||
# 初始化并启动情绪管理器
|
||||
mood_manager = MoodManager.get_instance()
|
||||
mood_manager.start_mood_update(update_interval=global_config.mood_update_interval)
|
||||
print("\033[1;32m[初始化]\033[0m 情绪管理器已启动")
|
||||
|
||||
logger.success("情绪管理器启动成功")
|
||||
|
||||
# 只启动表情包管理任务
|
||||
asyncio.create_task(emoji_manager.start_periodic_check(interval_MINS=global_config.EMOJI_CHECK_INTERVAL))
|
||||
await bot_schedule.initialize()
|
||||
bot_schedule.print_schedule()
|
||||
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def init_relationships():
|
||||
"""在 NoneBot2 启动时初始化关系管理器"""
|
||||
print("\033[1;32m[初始化]\033[0m 正在加载用户关系数据...")
|
||||
logger.debug("正在加载用户关系数据...")
|
||||
await relationship_manager.load_all_relationships()
|
||||
asyncio.create_task(relationship_manager._start_relationship_manager())
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: Bot):
|
||||
"""Bot连接成功时的处理"""
|
||||
global _message_manager_started
|
||||
print(f"\033[1;38;5;208m-----------{global_config.BOT_NICKNAME}成功连接!-----------\033[0m")
|
||||
logger.debug(f"-----------{global_config.BOT_NICKNAME}成功连接!-----------")
|
||||
await willing_manager.ensure_started()
|
||||
|
||||
|
||||
message_sender.set_bot(bot)
|
||||
print("\033[1;38;5;208m-----------消息发送器已启动!-----------\033[0m")
|
||||
|
||||
logger.success("-----------消息发送器已启动!-----------")
|
||||
|
||||
if not _message_manager_started:
|
||||
asyncio.create_task(message_manager.start_processor())
|
||||
_message_manager_started = True
|
||||
print("\033[1;38;5;208m-----------消息处理器已启动!-----------\033[0m")
|
||||
|
||||
logger.success("-----------消息处理器已启动!-----------")
|
||||
|
||||
asyncio.create_task(emoji_manager._periodic_scan(interval_MINS=global_config.EMOJI_REGISTER_INTERVAL))
|
||||
print("\033[1;38;5;208m-----------开始偷表情包!-----------\033[0m")
|
||||
logger.success("-----------开始偷表情包!-----------")
|
||||
asyncio.create_task(chat_manager._initialize())
|
||||
asyncio.create_task(chat_manager._auto_save_task())
|
||||
|
||||
|
||||
|
||||
@group_msg.handle()
|
||||
async def _(bot: Bot, event: GroupMessageEvent, state: T_State):
|
||||
await chat_bot.handle_message(event, bot)
|
||||
|
||||
|
||||
# 添加build_memory定时任务
|
||||
@scheduler.scheduled_job("interval", seconds=global_config.build_memory_interval, id="build_memory")
|
||||
async def build_memory_task():
|
||||
"""每build_memory_interval秒执行一次记忆构建"""
|
||||
print("\033[1;32m[记忆构建]\033[0m -------------------------------------------开始构建记忆-------------------------------------------")
|
||||
logger.debug(
|
||||
"[记忆构建]"
|
||||
"------------------------------------开始构建记忆--------------------------------------")
|
||||
start_time = time.time()
|
||||
await hippocampus.operation_build_memory(chat_size=20)
|
||||
end_time = time.time()
|
||||
print(f"\033[1;32m[记忆构建]\033[0m -------------------------------------------记忆构建完成:耗时: {end_time - start_time:.2f} 秒-------------------------------------------")
|
||||
|
||||
@scheduler.scheduled_job("interval", seconds=global_config.forget_memory_interval, id="forget_memory")
|
||||
logger.success(
|
||||
f"[记忆构建]--------------------------记忆构建完成:耗时: {end_time - start_time:.2f} "
|
||||
"秒-------------------------------------------")
|
||||
|
||||
|
||||
@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")
|
||||
async def merge_memory_task():
|
||||
@@ -133,9 +136,9 @@ async def merge_memory_task():
|
||||
# await hippocampus.operation_merge_memory(percentage=0.1)
|
||||
# print("\033[1;32m[记忆整合]\033[0m 记忆整合完成")
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", seconds=30, id="print_mood")
|
||||
async def print_mood_task():
|
||||
"""每30秒打印一次情绪状态"""
|
||||
mood_manager = MoodManager.get_instance()
|
||||
mood_manager.print_mood_status()
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
import time
|
||||
from random import random
|
||||
from loguru import logger
|
||||
@@ -31,10 +32,10 @@ class ChatBot:
|
||||
self._started = False
|
||||
self.mood_manager = MoodManager.get_instance() # 获取情绪管理器单例
|
||||
self.mood_manager.start_mood_update() # 启动情绪更新
|
||||
|
||||
|
||||
self.emoji_chance = 0.2 # 发送表情包的基础概率
|
||||
# self.message_streams = MessageStreamContainer()
|
||||
|
||||
|
||||
async def _ensure_started(self):
|
||||
"""确保所有任务已启动"""
|
||||
if not self._started:
|
||||
@@ -42,9 +43,9 @@ class ChatBot:
|
||||
|
||||
async def handle_message(self, event: GroupMessageEvent, bot: Bot) -> None:
|
||||
"""处理收到的群消息"""
|
||||
|
||||
|
||||
self.bot = bot # 更新 bot 实例
|
||||
|
||||
|
||||
# group_info = await bot.get_group_info(group_id=event.group_id)
|
||||
# sender_info = await bot.get_group_member_info(group_id=event.group_id, user_id=event.user_id, no_cache=True)
|
||||
|
||||
@@ -96,8 +97,17 @@ class ChatBot:
|
||||
# 过滤词
|
||||
for word in global_config.ban_words:
|
||||
if word in message.processed_plain_text:
|
||||
logger.info(f"\033[1;32m[{groupinfo.group_name}]{userinfo.user_nickname}:\033[0m {message.processed_plain_text}")
|
||||
logger.info(f"\033[1;32m[过滤词识别]\033[0m 消息中含有{word},filtered")
|
||||
logger.info(
|
||||
f"[{groupinfo.group_name}]{userinfo.user_nickname}:{message.processed_plain_text}")
|
||||
logger.info(f"[过滤词识别]消息中含有{word},filtered")
|
||||
return
|
||||
|
||||
# 正则表达式过滤
|
||||
for pattern in global_config.ban_msgs_regex:
|
||||
if re.search(pattern, message.raw_message):
|
||||
logger.info(
|
||||
f"[{message.group_name}]{message.user_nickname}:{message.raw_message}")
|
||||
logger.info(f"[正则表达式过滤]消息匹配到{pattern},filtered")
|
||||
return
|
||||
|
||||
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(messageinfo.time))
|
||||
@@ -107,8 +117,9 @@ class ChatBot:
|
||||
# topic=await topic_identifier.identify_topic_llm(message.processed_plain_text)
|
||||
topic = ''
|
||||
interested_rate = 0
|
||||
interested_rate = await hippocampus.memory_activate_value(message.processed_plain_text)/100
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 对{message.processed_plain_text}的激活度:---------------------------------------{interested_rate}\n")
|
||||
interested_rate = await hippocampus.memory_activate_value(message.processed_plain_text) / 100
|
||||
logger.debug(f"对{message.processed_plain_text}"
|
||||
f"的激活度:{interested_rate}")
|
||||
# logger.info(f"\033[1;32m[主题识别]\033[0m 使用{global_config.topic_extract}主题: {topic}")
|
||||
|
||||
await self.storage.store_message(message,chat, topic[0] if topic else None)
|
||||
@@ -124,7 +135,10 @@ class ChatBot:
|
||||
)
|
||||
current_willing = willing_manager.get_willing(chat_stream=chat)
|
||||
|
||||
print(f"\033[1;32m[{current_time}][{chat.group_info.group_name}]{chat.user_info.user_nickname}:\033[0m {message.processed_plain_text}\033[1;36m[回复意愿:{current_willing:.2f}][概率:{reply_probability * 100:.1f}%]\033[0m")
|
||||
logger.info(
|
||||
f"[{current_time}][{chat.group_info.group_name}]{chat.user_info.user_nickname}:"
|
||||
f"{message.processed_plain_text}[回复意愿:{current_willing:.2f}][概率:{reply_probability * 100:.1f}%]"
|
||||
)
|
||||
|
||||
response = None
|
||||
|
||||
@@ -159,13 +173,13 @@ class ChatBot:
|
||||
thinking_message = msg
|
||||
container.messages.remove(msg)
|
||||
break
|
||||
|
||||
|
||||
# 如果找不到思考消息,直接返回
|
||||
if not thinking_message:
|
||||
print(f"\033[1;33m[警告]\033[0m 未找到对应的思考消息,可能已超时被移除")
|
||||
logger.warning("未找到对应的思考消息,可能已超时被移除")
|
||||
return
|
||||
|
||||
#记录开始思考的时间,避免从思考到回复的时间太久
|
||||
|
||||
# 记录开始思考的时间,避免从思考到回复的时间太久
|
||||
thinking_start_time = thinking_message.thinking_start_time
|
||||
message_set = MessageSet(chat, think_id)
|
||||
message_set = MessageSet(chat, think_id)
|
||||
@@ -175,7 +189,7 @@ class ChatBot:
|
||||
mark_head = False
|
||||
for msg in response:
|
||||
# print(f"\033[1;32m[回复内容]\033[0m {msg}")
|
||||
#通过时间改变时间戳
|
||||
# 通过时间改变时间戳
|
||||
typing_time = calculate_typing_time(msg)
|
||||
accu_typing_time += typing_time
|
||||
timepoint = tinking_time_point + accu_typing_time
|
||||
@@ -193,19 +207,19 @@ class ChatBot:
|
||||
if not mark_head:
|
||||
mark_head = True
|
||||
message_set.add_message(bot_message)
|
||||
|
||||
#message_set 可以直接加入 message_manager
|
||||
|
||||
# message_set 可以直接加入 message_manager
|
||||
# print(f"\033[1;32m[回复]\033[0m 将回复载入发送容器")
|
||||
message_manager.add_message(message_set)
|
||||
|
||||
|
||||
bot_response_time = tinking_time_point
|
||||
|
||||
if random() < global_config.emoji_chance:
|
||||
emoji_raw = await emoji_manager.get_emoji_for_text(response)
|
||||
|
||||
|
||||
# 检查是否 <没有找到> emoji
|
||||
if emoji_raw != None:
|
||||
emoji_path,discription = emoji_raw
|
||||
emoji_path, description = emoji_raw
|
||||
|
||||
emoji_cq = image_path_to_base64(emoji_path)
|
||||
|
||||
@@ -226,8 +240,8 @@ class ChatBot:
|
||||
)
|
||||
message_manager.add_message(bot_message)
|
||||
emotion = await self.gpt._get_emotion_tags(raw_content)
|
||||
print(f"为 '{response}' 获取到的情感标签为:{emotion}")
|
||||
valuedict={
|
||||
logger.debug(f"为 '{response}' 获取到的情感标签为:{emotion}")
|
||||
valuedict = {
|
||||
'happy': 0.5,
|
||||
'angry': -1,
|
||||
'sad': -0.5,
|
||||
@@ -240,9 +254,10 @@ class ChatBot:
|
||||
# 使用情绪管理器更新情绪
|
||||
self.mood_manager.update_mood_from_emotion(emotion[0], global_config.mood_intensity_factor)
|
||||
|
||||
willing_manager.change_reply_willing_after_sent(
|
||||
chat_stream=chat
|
||||
)
|
||||
# willing_manager.change_reply_willing_after_sent(
|
||||
# chat_stream=chat
|
||||
# )
|
||||
|
||||
|
||||
# 创建全局ChatBot实例
|
||||
chat_bot = ChatBot()
|
||||
chat_bot = ChatBot()
|
||||
|
||||
@@ -1,46 +1,54 @@
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import tomli
|
||||
from loguru import logger
|
||||
from packaging import version
|
||||
from packaging.version import Version, InvalidVersion
|
||||
from packaging.specifiers import SpecifierSet, InvalidSpecifier
|
||||
|
||||
|
||||
@dataclass
|
||||
class BotConfig:
|
||||
"""机器人配置类"""
|
||||
"""机器人配置类"""
|
||||
|
||||
INNER_VERSION: Version = None
|
||||
|
||||
BOT_QQ: Optional[int] = 1
|
||||
BOT_NICKNAME: Optional[str] = None
|
||||
|
||||
BOT_ALIAS_NAMES: List[str] = field(default_factory=list) # 别名,可以通过这个叫它
|
||||
|
||||
# 消息处理相关配置
|
||||
MIN_TEXT_LENGTH: int = 2 # 最小处理文本长度
|
||||
MAX_CONTEXT_SIZE: int = 15 # 上下文最大消息数
|
||||
emoji_chance: float = 0.2 # 发送表情包的基础概率
|
||||
|
||||
|
||||
ENABLE_PIC_TRANSLATE: bool = True # 是否启用图片翻译
|
||||
|
||||
|
||||
talk_allowed_groups = set()
|
||||
talk_frequency_down_groups = set()
|
||||
thinking_timeout: int = 100 # 思考时间
|
||||
|
||||
|
||||
response_willing_amplifier: float = 1.0 # 回复意愿放大系数
|
||||
response_interested_rate_amplifier: float = 1.0 # 回复兴趣度放大系数
|
||||
down_frequency_rate: float = 3.5 # 降低回复频率的群组回复意愿降低系数
|
||||
|
||||
|
||||
ban_user_id = set()
|
||||
|
||||
|
||||
build_memory_interval: int = 30 # 记忆构建间隔(秒)
|
||||
forget_memory_interval: int = 300 # 记忆遗忘间隔(秒)
|
||||
EMOJI_CHECK_INTERVAL: int = 120 # 表情包检查间隔(分钟)
|
||||
EMOJI_REGISTER_INTERVAL: int = 10 # 表情包注册间隔(分钟)
|
||||
EMOJI_SAVE: bool = True # 偷表情包
|
||||
EMOJI_CHECK: bool = False #是否开启过滤
|
||||
EMOJI_CHECK_PROMPT: str = "符合公序良俗" # 表情包过滤要求
|
||||
EMOJI_CHECK: bool = False # 是否开启过滤
|
||||
EMOJI_CHECK_PROMPT: str = "符合公序良俗" # 表情包过滤要求
|
||||
|
||||
ban_words = set()
|
||||
ban_msgs_regex = set()
|
||||
|
||||
max_response_length: int = 1024 # 最大回复长度
|
||||
|
||||
|
||||
# 模型配置
|
||||
llm_reasoning: Dict[str, str] = field(default_factory=lambda: {})
|
||||
llm_reasoning_minor: Dict[str, str] = field(default_factory=lambda: {})
|
||||
@@ -56,176 +64,359 @@ class BotConfig:
|
||||
MODEL_R1_PROBABILITY: float = 0.8 # R1模型概率
|
||||
MODEL_V3_PROBABILITY: float = 0.1 # V3模型概率
|
||||
MODEL_R1_DISTILL_PROBABILITY: float = 0.1 # R1蒸馏模型概率
|
||||
|
||||
|
||||
enable_advance_output: bool = False # 是否启用高级输出
|
||||
enable_kuuki_read: bool = True # 是否启用读空气功能
|
||||
|
||||
mood_update_interval: float = 1.0 # 情绪更新间隔 单位秒
|
||||
mood_decay_rate: float = 0.95 # 情绪衰减率
|
||||
mood_intensity_factor: float = 0.7 # 情绪强度因子
|
||||
enable_kuuki_read: bool = True # 是否启用读空气功能
|
||||
|
||||
mood_update_interval: float = 1.0 # 情绪更新间隔 单位秒
|
||||
mood_decay_rate: float = 0.95 # 情绪衰减率
|
||||
mood_intensity_factor: float = 0.7 # 情绪强度因子
|
||||
|
||||
keywords_reaction_rules = [] # 关键词回复规则
|
||||
|
||||
chinese_typo_enable = True # 是否启用中文错别字生成器
|
||||
chinese_typo_error_rate = 0.03 # 单字替换概率
|
||||
chinese_typo_min_freq = 7 # 最小字频阈值
|
||||
chinese_typo_tone_error_rate = 0.2 # 声调错误概率
|
||||
chinese_typo_word_replace_rate = 0.02 # 整词替换概率
|
||||
|
||||
# 默认人设
|
||||
PROMPT_PERSONALITY=[
|
||||
PROMPT_PERSONALITY = [
|
||||
"曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧",
|
||||
"是一个女大学生,你有黑色头发,你会刷小红书",
|
||||
"是一个女大学生,你会刷b站,对ACG文化感兴趣"
|
||||
"是一个女大学生,你会刷b站,对ACG文化感兴趣",
|
||||
]
|
||||
PROMPT_SCHEDULE_GEN="一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书"
|
||||
|
||||
PERSONALITY_1: float = 0.6 # 第一种人格概率
|
||||
PERSONALITY_2: float = 0.3 # 第二种人格概率
|
||||
PERSONALITY_3: float = 0.1 # 第三种人格概率
|
||||
|
||||
|
||||
PROMPT_SCHEDULE_GEN = "一个曾经学习地质,现在学习心理学和脑科学的女大学生,喜欢刷qq,贴吧,知乎和小红书"
|
||||
|
||||
PERSONALITY_1: float = 0.6 # 第一种人格概率
|
||||
PERSONALITY_2: float = 0.3 # 第二种人格概率
|
||||
PERSONALITY_3: float = 0.1 # 第三种人格概率
|
||||
|
||||
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
|
||||
|
||||
|
||||
@classmethod
|
||||
def convert_to_specifierset(cls, value: str) -> SpecifierSet:
|
||||
"""将 字符串 版本表达式转换成 SpecifierSet
|
||||
Args:
|
||||
value[str]: 版本表达式(字符串)
|
||||
Returns:
|
||||
SpecifierSet
|
||||
"""
|
||||
|
||||
try:
|
||||
converted = SpecifierSet(value)
|
||||
except InvalidSpecifier:
|
||||
logger.error(f"{value} 分类使用了错误的版本约束表达式\n", "请阅读 https://semver.org/lang/zh-CN/ 修改代码")
|
||||
exit(1)
|
||||
|
||||
return converted
|
||||
|
||||
@classmethod
|
||||
def get_config_version(cls, toml: dict) -> Version:
|
||||
"""提取配置文件的 SpecifierSet 版本数据
|
||||
Args:
|
||||
toml[dict]: 输入的配置文件字典
|
||||
Returns:
|
||||
Version
|
||||
"""
|
||||
|
||||
if "inner" in toml:
|
||||
try:
|
||||
config_version: str = toml["inner"]["version"]
|
||||
except KeyError as e:
|
||||
logger.error("配置文件中 inner 段 不存在, 这是错误的配置文件")
|
||||
raise KeyError(f"配置文件中 inner 段 不存在 {e}, 这是错误的配置文件") from e
|
||||
else:
|
||||
toml["inner"] = {"version": "0.0.0"}
|
||||
config_version = toml["inner"]["version"]
|
||||
|
||||
try:
|
||||
ver = version.parse(config_version)
|
||||
except InvalidVersion as e:
|
||||
logger.error(
|
||||
"配置文件中 inner段 的 version 键是错误的版本描述\n"
|
||||
"请阅读 https://semver.org/lang/zh-CN/ 修改配置,并参考本项目指定的模板进行修改\n"
|
||||
"本项目在不同的版本下有不同的模板,请注意识别"
|
||||
)
|
||||
raise InvalidVersion("配置文件中 inner段 的 version 键是错误的版本描述\n") from e
|
||||
|
||||
return ver
|
||||
|
||||
@classmethod
|
||||
def load_config(cls, config_path: str = None) -> "BotConfig":
|
||||
"""从TOML配置文件加载配置"""
|
||||
config = cls()
|
||||
|
||||
def personality(parent: dict):
|
||||
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)
|
||||
logger.info(f"载入自定义日程prompt:{personality_config.get('prompt_schedule', config.PROMPT_SCHEDULE_GEN)}")
|
||||
config.PROMPT_SCHEDULE_GEN = personality_config.get("prompt_schedule", config.PROMPT_SCHEDULE_GEN)
|
||||
|
||||
if config.INNER_VERSION in SpecifierSet(">=0.0.2"):
|
||||
config.PERSONALITY_1 = personality_config.get("personality_1_probability", config.PERSONALITY_1)
|
||||
config.PERSONALITY_2 = personality_config.get("personality_2_probability", config.PERSONALITY_2)
|
||||
config.PERSONALITY_3 = personality_config.get("personality_3_probability", config.PERSONALITY_3)
|
||||
|
||||
def emoji(parent: dict):
|
||||
emoji_config = parent["emoji"]
|
||||
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)
|
||||
|
||||
def cq_code(parent: dict):
|
||||
cq_code_config = parent["cq_code"]
|
||||
config.ENABLE_PIC_TRANSLATE = cq_code_config.get("enable_pic_translate", config.ENABLE_PIC_TRANSLATE)
|
||||
|
||||
def bot(parent: dict):
|
||||
# 机器人基础配置
|
||||
bot_config = parent["bot"]
|
||||
bot_qq = bot_config.get("qq")
|
||||
config.BOT_QQ = int(bot_qq)
|
||||
config.BOT_NICKNAME = bot_config.get("nickname", config.BOT_NICKNAME)
|
||||
|
||||
if config.INNER_VERSION in SpecifierSet(">=0.0.5"):
|
||||
config.BOT_ALIAS_NAMES = bot_config.get("alias_names", config.BOT_ALIAS_NAMES)
|
||||
|
||||
def response(parent: dict):
|
||||
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.max_response_length = response_config.get("max_response_length", config.max_response_length)
|
||||
|
||||
def model(parent: dict):
|
||||
# 加载模型配置
|
||||
model_config: dict = parent["model"]
|
||||
|
||||
config_list = [
|
||||
"llm_reasoning",
|
||||
"llm_reasoning_minor",
|
||||
"llm_normal",
|
||||
"llm_normal_minor",
|
||||
"llm_topic_judge",
|
||||
"llm_summary_by_topic",
|
||||
"llm_emotion_judge",
|
||||
"vlm",
|
||||
"embedding",
|
||||
"moderation",
|
||||
]
|
||||
|
||||
for item in config_list:
|
||||
if item in model_config:
|
||||
cfg_item: dict = model_config[item]
|
||||
|
||||
# base_url 的例子: SILICONFLOW_BASE_URL
|
||||
# key 的例子: SILICONFLOW_KEY
|
||||
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
|
||||
|
||||
elif config.INNER_VERSION in SpecifierSet(">=0.0.1"):
|
||||
stable_item = ["name", "pri_in", "pri_out"]
|
||||
pricing_item = ["pri_in", "pri_out"]
|
||||
# 从配置中原始拷贝稳定字段
|
||||
for i in stable_item:
|
||||
# 如果 字段 属于计费项 且获取不到,那默认值是 0
|
||||
if i in pricing_item and i not in cfg_item:
|
||||
cfg_target[i] = 0
|
||||
else:
|
||||
# 没有特殊情况则原样复制
|
||||
try:
|
||||
cfg_target[i] = cfg_item[i]
|
||||
except KeyError as e:
|
||||
logger.error(f"{item} 中的必要字段不存在,请检查")
|
||||
raise KeyError(f"{item} 中的必要字段 {e} 不存在,请检查") from e
|
||||
|
||||
provider = cfg_item.get("provider")
|
||||
if provider is None:
|
||||
logger.error(f"provider 字段在模型配置 {item} 中不存在,请检查")
|
||||
raise KeyError(f"provider 字段在模型配置 {item} 中不存在,请检查")
|
||||
|
||||
cfg_target["base_url"] = f"{provider}_BASE_URL"
|
||||
cfg_target["key"] = f"{provider}_KEY"
|
||||
|
||||
# 如果 列表中的项目在 model_config 中,利用反射来设置对应项目
|
||||
setattr(config, item, cfg_target)
|
||||
else:
|
||||
logger.error(f"模型 {item} 在config中不存在,请检查")
|
||||
raise KeyError(f"模型 {item} 在config中不存在,请检查")
|
||||
|
||||
def message(parent: dict):
|
||||
msg_config = parent["message"]
|
||||
config.MIN_TEXT_LENGTH = msg_config.get("min_text_length", config.MIN_TEXT_LENGTH)
|
||||
config.MAX_CONTEXT_SIZE = msg_config.get("max_context_size", config.MAX_CONTEXT_SIZE)
|
||||
config.emoji_chance = msg_config.get("emoji_chance", config.emoji_chance)
|
||||
config.ban_words = msg_config.get("ban_words", config.ban_words)
|
||||
|
||||
if config.INNER_VERSION in SpecifierSet(">=0.0.2"):
|
||||
config.thinking_timeout = msg_config.get("thinking_timeout", config.thinking_timeout)
|
||||
config.response_willing_amplifier = msg_config.get(
|
||||
"response_willing_amplifier", config.response_willing_amplifier
|
||||
)
|
||||
config.response_interested_rate_amplifier = msg_config.get(
|
||||
"response_interested_rate_amplifier", config.response_interested_rate_amplifier
|
||||
)
|
||||
config.down_frequency_rate = msg_config.get("down_frequency_rate", config.down_frequency_rate)
|
||||
|
||||
if config.INNER_VERSION in SpecifierSet(">=0.0.6"):
|
||||
config.ban_msgs_regex = msg_config.get("ban_msgs_regex", config.ban_msgs_regex)
|
||||
|
||||
def memory(parent: dict):
|
||||
memory_config = parent["memory"]
|
||||
config.build_memory_interval = memory_config.get("build_memory_interval", config.build_memory_interval)
|
||||
config.forget_memory_interval = memory_config.get("forget_memory_interval", config.forget_memory_interval)
|
||||
|
||||
# 在版本 >= 0.0.4 时才处理新增的配置项
|
||||
if config.INNER_VERSION in SpecifierSet(">=0.0.4"):
|
||||
config.memory_ban_words = set(memory_config.get("memory_ban_words", []))
|
||||
|
||||
def mood(parent: dict):
|
||||
mood_config = parent["mood"]
|
||||
config.mood_update_interval = mood_config.get("mood_update_interval", config.mood_update_interval)
|
||||
config.mood_decay_rate = mood_config.get("mood_decay_rate", config.mood_decay_rate)
|
||||
config.mood_intensity_factor = mood_config.get("mood_intensity_factor", config.mood_intensity_factor)
|
||||
|
||||
def keywords_reaction(parent: dict):
|
||||
keywords_reaction_config = parent["keywords_reaction"]
|
||||
if keywords_reaction_config.get("enable", False):
|
||||
config.keywords_reaction_rules = keywords_reaction_config.get("rules", config.keywords_reaction_rules)
|
||||
|
||||
def chinese_typo(parent: dict):
|
||||
chinese_typo_config = parent["chinese_typo"]
|
||||
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
|
||||
)
|
||||
|
||||
def groups(parent: dict):
|
||||
groups_config = parent["groups"]
|
||||
config.talk_allowed_groups = set(groups_config.get("talk_allowed", []))
|
||||
config.talk_frequency_down_groups = set(groups_config.get("talk_frequency_down", []))
|
||||
config.ban_user_id = set(groups_config.get("ban_user_id", []))
|
||||
|
||||
def others(parent: dict):
|
||||
others_config = parent["others"]
|
||||
config.enable_advance_output = others_config.get("enable_advance_output", config.enable_advance_output)
|
||||
config.enable_kuuki_read = others_config.get("enable_kuuki_read", config.enable_kuuki_read)
|
||||
|
||||
# 版本表达式:>=1.0.0,<2.0.0
|
||||
# 允许字段:func: method, support: str, notice: str, necessary: bool
|
||||
# 如果使用 notice 字段,在该组配置加载时,会展示该字段对用户的警示
|
||||
# 例如:"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"},
|
||||
}
|
||||
|
||||
# 原地修改,将 字符串版本表达式 转换成 版本对象
|
||||
for key in include_configs:
|
||||
item_support = include_configs[key]["support"]
|
||||
include_configs[key]["support"] = cls.convert_to_specifierset(item_support)
|
||||
|
||||
if os.path.exists(config_path):
|
||||
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)
|
||||
|
||||
if 'personality' in toml_dict:
|
||||
personality_config=toml_dict['personality']
|
||||
personality=personality_config.get('prompt_personality')
|
||||
if len(personality) >= 2:
|
||||
logger.info(f"载入自定义人格:{personality}")
|
||||
config.PROMPT_PERSONALITY=personality_config.get('prompt_personality',config.PROMPT_PERSONALITY)
|
||||
logger.info(f"载入自定义日程prompt:{personality_config.get('prompt_schedule',config.PROMPT_SCHEDULE_GEN)}")
|
||||
config.PROMPT_SCHEDULE_GEN=personality_config.get('prompt_schedule',config.PROMPT_SCHEDULE_GEN)
|
||||
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)
|
||||
|
||||
if "emoji" in toml_dict:
|
||||
emoji_config = toml_dict["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)
|
||||
|
||||
if "cq_code" in toml_dict:
|
||||
cq_code_config = toml_dict["cq_code"]
|
||||
config.ENABLE_PIC_TRANSLATE = cq_code_config.get("enable_pic_translate", config.ENABLE_PIC_TRANSLATE)
|
||||
|
||||
# 机器人基础配置
|
||||
if "bot" in toml_dict:
|
||||
bot_config = toml_dict["bot"]
|
||||
bot_qq = bot_config.get("qq")
|
||||
config.BOT_QQ = int(bot_qq)
|
||||
config.BOT_NICKNAME = bot_config.get("nickname", config.BOT_NICKNAME)
|
||||
|
||||
if "response" in toml_dict:
|
||||
response_config = toml_dict["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.max_response_length = response_config.get("max_response_length", config.max_response_length)
|
||||
|
||||
# 加载模型配置
|
||||
if "model" in toml_dict:
|
||||
model_config = toml_dict["model"]
|
||||
|
||||
if "llm_reasoning" in model_config:
|
||||
config.llm_reasoning = model_config["llm_reasoning"]
|
||||
|
||||
if "llm_reasoning_minor" in model_config:
|
||||
config.llm_reasoning_minor = model_config["llm_reasoning_minor"]
|
||||
|
||||
if "llm_normal" in model_config:
|
||||
config.llm_normal = model_config["llm_normal"]
|
||||
|
||||
if "llm_normal_minor" in model_config:
|
||||
config.llm_normal_minor = model_config["llm_normal_minor"]
|
||||
|
||||
if "llm_topic_judge" in model_config:
|
||||
config.llm_topic_judge = model_config["llm_topic_judge"]
|
||||
|
||||
if "llm_summary_by_topic" in model_config:
|
||||
config.llm_summary_by_topic = model_config["llm_summary_by_topic"]
|
||||
|
||||
if "llm_emotion_judge" in model_config:
|
||||
config.llm_emotion_judge = model_config["llm_emotion_judge"]
|
||||
|
||||
if "vlm" in model_config:
|
||||
config.vlm = model_config["vlm"]
|
||||
|
||||
if "embedding" in model_config:
|
||||
config.embedding = model_config["embedding"]
|
||||
|
||||
if "moderation" in model_config:
|
||||
config.moderation = model_config["moderation"]
|
||||
|
||||
# 消息配置
|
||||
if "message" in toml_dict:
|
||||
msg_config = toml_dict["message"]
|
||||
config.MIN_TEXT_LENGTH = msg_config.get("min_text_length", config.MIN_TEXT_LENGTH)
|
||||
config.MAX_CONTEXT_SIZE = msg_config.get("max_context_size", config.MAX_CONTEXT_SIZE)
|
||||
config.emoji_chance = msg_config.get("emoji_chance", config.emoji_chance)
|
||||
config.ban_words=msg_config.get("ban_words",config.ban_words)
|
||||
config.thinking_timeout = msg_config.get("thinking_timeout", config.thinking_timeout)
|
||||
config.response_willing_amplifier = msg_config.get("response_willing_amplifier", config.response_willing_amplifier)
|
||||
config.response_interested_rate_amplifier = msg_config.get("response_interested_rate_amplifier", config.response_interested_rate_amplifier)
|
||||
config.down_frequency_rate = msg_config.get("down_frequency_rate", config.down_frequency_rate)
|
||||
# 获取配置文件版本
|
||||
config.INNER_VERSION = cls.get_config_version(toml_dict)
|
||||
|
||||
# 如果在配置中找到了需要的项,调用对应项的闭包函数处理
|
||||
for key in include_configs:
|
||||
if key in toml_dict:
|
||||
group_specifierset: SpecifierSet = include_configs[key]["support"]
|
||||
|
||||
# 检查配置文件版本是否在支持范围内
|
||||
if config.INNER_VERSION in group_specifierset:
|
||||
# 如果版本在支持范围内,检查是否存在通知
|
||||
if "notice" in include_configs[key]:
|
||||
logger.warning(include_configs[key]["notice"])
|
||||
|
||||
include_configs[key]["func"](toml_dict)
|
||||
|
||||
else:
|
||||
# 如果版本不在支持范围内,崩溃并提示用户
|
||||
logger.error(
|
||||
f"配置文件中的 '{key}' 字段的版本 ({config.INNER_VERSION}) 不在支持范围内。\n"
|
||||
f"当前程序仅支持以下版本范围: {group_specifierset}"
|
||||
)
|
||||
raise InvalidVersion(f"当前程序仅支持以下版本范围: {group_specifierset}")
|
||||
|
||||
# 如果 necessary 项目存在,而且显式声明是 False,进入特殊处理
|
||||
elif "necessary" in include_configs[key] and include_configs[key].get("necessary") is False:
|
||||
# 通过 pass 处理的项虽然直接忽略也是可以的,但是为了不增加理解困难,依然需要在这里显式处理
|
||||
if key == "keywords_reaction":
|
||||
pass
|
||||
|
||||
else:
|
||||
# 如果用户根本没有需要的配置项,提示缺少配置
|
||||
logger.error(f"配置文件中缺少必需的字段: '{key}'")
|
||||
raise KeyError(f"配置文件中缺少必需的字段: '{key}'")
|
||||
|
||||
logger.success(f"成功加载配置文件: {config_path}")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
if "memory" in toml_dict:
|
||||
memory_config = toml_dict["memory"]
|
||||
config.build_memory_interval = memory_config.get("build_memory_interval", config.build_memory_interval)
|
||||
config.forget_memory_interval = memory_config.get("forget_memory_interval", config.forget_memory_interval)
|
||||
|
||||
if "mood" in toml_dict:
|
||||
mood_config = toml_dict["mood"]
|
||||
config.mood_update_interval = mood_config.get("mood_update_interval", config.mood_update_interval)
|
||||
config.mood_decay_rate = mood_config.get("mood_decay_rate", config.mood_decay_rate)
|
||||
config.mood_intensity_factor = mood_config.get("mood_intensity_factor", config.mood_intensity_factor)
|
||||
|
||||
# 群组配置
|
||||
if "groups" in toml_dict:
|
||||
groups_config = toml_dict["groups"]
|
||||
config.talk_allowed_groups = set(groups_config.get("talk_allowed", []))
|
||||
config.talk_frequency_down_groups = set(groups_config.get("talk_frequency_down", []))
|
||||
config.ban_user_id = set(groups_config.get("ban_user_id", []))
|
||||
|
||||
if "others" in toml_dict:
|
||||
others_config = toml_dict["others"]
|
||||
config.enable_advance_output = others_config.get("enable_advance_output", config.enable_advance_output)
|
||||
config.enable_kuuki_read = others_config.get("enable_kuuki_read", config.enable_kuuki_read)
|
||||
|
||||
logger.success(f"成功加载配置文件: {config_path}")
|
||||
|
||||
return config
|
||||
|
||||
# 获取配置文件路径
|
||||
|
||||
bot_config_floder_path = BotConfig.get_config_dir()
|
||||
print(f"正在品鉴配置文件目录: {bot_config_floder_path}")
|
||||
logger.debug(f"正在品鉴配置文件目录: {bot_config_floder_path}")
|
||||
|
||||
bot_config_path = os.path.join(bot_config_floder_path, "bot_config.toml")
|
||||
|
||||
if os.path.exists(bot_config_path):
|
||||
# 如果开发环境配置文件不存在,则使用默认配置文件
|
||||
print(f"异常的新鲜,异常的美味: {bot_config_path}")
|
||||
logger.debug(f"异常的新鲜,异常的美味: {bot_config_path}")
|
||||
logger.info("使用bot配置文件")
|
||||
else:
|
||||
logger.info("没有找到美味")
|
||||
# 配置文件不存在
|
||||
logger.error("配置文件不存在,请检查路径: {bot_config_path}")
|
||||
raise FileNotFoundError(f"配置文件不存在: {bot_config_path}")
|
||||
|
||||
global_config = BotConfig.load_config(config_path=bot_config_path)
|
||||
|
||||
|
||||
if not global_config.enable_advance_output:
|
||||
logger.remove()
|
||||
pass
|
||||
|
||||
|
||||
@@ -170,11 +170,11 @@ class CQCode:
|
||||
|
||||
except (requests.exceptions.SSLError, requests.exceptions.HTTPError) as e:
|
||||
if retry == max_retries - 1:
|
||||
print(f"\033[1;31m[致命错误]\033[0m 最终请求失败: {str(e)}")
|
||||
logger.error(f"最终请求失败: {str(e)}")
|
||||
time.sleep(1.5**retry) # 指数退避
|
||||
|
||||
except Exception as e:
|
||||
print(f"\033[1;33m[未知错误]\033[0m {str(e)}")
|
||||
except Exception:
|
||||
logger.exception("[未知错误]")
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
@@ -24,24 +24,26 @@ image_manager = ImageManager()
|
||||
class EmojiManager:
|
||||
_instance = None
|
||||
EMOJI_DIR = "data/emoji" # 表情包存储目录
|
||||
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
cls._instance.db = None
|
||||
cls._instance._initialized = False
|
||||
return cls._instance
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.db = Database.get_instance()
|
||||
self._scan_task = None
|
||||
self.vlm = LLM_request(model=global_config.vlm, temperature=0.3, max_tokens=1000)
|
||||
self.llm_emotion_judge = LLM_request(model=global_config.llm_normal_minor, max_tokens=60,temperature=0.8) #更高的温度,更少的token(后续可以根据情绪来调整温度)
|
||||
|
||||
self.llm_emotion_judge = LLM_request(model=global_config.llm_normal_minor, max_tokens=60,
|
||||
temperature=0.8) # 更高的温度,更少的token(后续可以根据情绪来调整温度)
|
||||
|
||||
|
||||
def _ensure_emoji_dir(self):
|
||||
"""确保表情存储目录存在"""
|
||||
os.makedirs(self.EMOJI_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def initialize(self):
|
||||
"""初始化数据库连接和表情目录"""
|
||||
if not self._initialized:
|
||||
@@ -52,16 +54,16 @@ class EmojiManager:
|
||||
self._initialized = True
|
||||
# 启动时执行一次完整性检查
|
||||
self.check_emoji_file_integrity()
|
||||
except Exception as e:
|
||||
logger.error(f"初始化表情管理器失败: {str(e)}")
|
||||
|
||||
except Exception:
|
||||
logger.exception("初始化表情管理器失败")
|
||||
|
||||
def _ensure_db(self):
|
||||
"""确保数据库已初始化"""
|
||||
if not self._initialized:
|
||||
self.initialize()
|
||||
if not self._initialized:
|
||||
raise RuntimeError("EmojiManager not initialized")
|
||||
|
||||
|
||||
def _ensure_emoji_collection(self):
|
||||
"""确保emoji集合存在并创建索引
|
||||
|
||||
@@ -78,7 +80,7 @@ class EmojiManager:
|
||||
self.db.db.create_collection('emoji')
|
||||
self.db.db.emoji.create_index([('embedding', '2dsphere')])
|
||||
self.db.db.emoji.create_index([('filename', 1)], unique=True)
|
||||
|
||||
|
||||
def record_usage(self, emoji_id: str):
|
||||
"""记录表情使用次数"""
|
||||
try:
|
||||
@@ -104,9 +106,9 @@ class EmojiManager:
|
||||
"""
|
||||
try:
|
||||
self._ensure_db()
|
||||
|
||||
|
||||
# 获取文本的embedding
|
||||
text_for_search= await self._get_kimoji_for_text(text)
|
||||
text_for_search = await self._get_kimoji_for_text(text)
|
||||
if not text_for_search:
|
||||
logger.error("无法获取文本的情绪")
|
||||
return None
|
||||
@@ -114,15 +116,15 @@ class EmojiManager:
|
||||
if not text_embedding:
|
||||
logger.error("无法获取文本的embedding")
|
||||
return None
|
||||
|
||||
|
||||
try:
|
||||
# 获取所有表情包
|
||||
all_emojis = list(self.db.db.emoji.find({}, {'_id': 1, 'path': 1, 'embedding': 1, 'discription': 1}))
|
||||
|
||||
all_emojis = list(self.db.db.emoji.find({}, {'_id': 1, 'path': 1, 'embedding': 1, 'description': 1}))
|
||||
|
||||
if not all_emojis:
|
||||
logger.warning("数据库中没有任何表情包")
|
||||
return None
|
||||
|
||||
|
||||
# 计算余弦相似度并排序
|
||||
def cosine_similarity(v1, v2):
|
||||
if not v1 or not v2:
|
||||
@@ -133,23 +135,23 @@ class EmojiManager:
|
||||
if norm_v1 == 0 or norm_v2 == 0:
|
||||
return 0
|
||||
return dot_product / (norm_v1 * norm_v2)
|
||||
|
||||
|
||||
# 计算所有表情包与输入文本的相似度
|
||||
emoji_similarities = [
|
||||
(emoji, cosine_similarity(text_embedding, emoji.get('embedding', [])))
|
||||
for emoji in all_emojis
|
||||
]
|
||||
|
||||
|
||||
# 按相似度降序排序
|
||||
emoji_similarities.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
|
||||
# 获取前3个最相似的表情包
|
||||
top_10_emojis = emoji_similarities[:10 if len(emoji_similarities) > 10 else len(emoji_similarities)]
|
||||
|
||||
if not top_10_emojis:
|
||||
logger.warning("未找到匹配的表情包")
|
||||
return None
|
||||
|
||||
|
||||
# 从前3个中随机选择一个
|
||||
selected_emoji, similarity = random.choice(top_10_emojis)
|
||||
|
||||
@@ -159,16 +161,17 @@ class EmojiManager:
|
||||
{'_id': selected_emoji['_id']},
|
||||
{'$inc': {'usage_count': 1}}
|
||||
)
|
||||
logger.success(f"找到匹配的表情包: {selected_emoji.get('discription', '无描述')} (相似度: {similarity:.4f})")
|
||||
logger.success(
|
||||
f"找到匹配的表情包: {selected_emoji.get('description', '无描述')} (相似度: {similarity:.4f})")
|
||||
# 稍微改一下文本描述,不然容易产生幻觉,描述已经包含 表情包 了
|
||||
return selected_emoji['path'],"[ %s ]" % selected_emoji.get('discription', '无描述')
|
||||
|
||||
return selected_emoji['path'], "[ %s ]" % selected_emoji.get('description', '无描述')
|
||||
|
||||
except Exception as search_error:
|
||||
logger.error(f"搜索表情包失败: {str(search_error)}")
|
||||
return None
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取表情包失败: {str(e)}")
|
||||
return None
|
||||
@@ -185,31 +188,31 @@ class EmojiManager:
|
||||
except Exception as e:
|
||||
logger.error(f"获取标签失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
async def _check_emoji(self, image_base64: str) -> str:
|
||||
try:
|
||||
prompt = f'这是一个表情包,请回答这个表情包是否满足\"{global_config.EMOJI_CHECK_PROMPT}\"的要求,是则回答是,否则回答否,不要出现任何其他内容'
|
||||
|
||||
|
||||
content, _ = await self.vlm.generate_response_for_image(prompt, image_base64)
|
||||
logger.debug(f"输出描述: {content}")
|
||||
return content
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取标签失败: {str(e)}")
|
||||
return None
|
||||
|
||||
async def _get_kimoji_for_text(self, text:str):
|
||||
|
||||
async def _get_kimoji_for_text(self, text: str):
|
||||
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
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取标签失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
async def scan_new_emojis(self):
|
||||
"""扫描新的表情包"""
|
||||
try:
|
||||
@@ -217,8 +220,9 @@ class EmojiManager:
|
||||
os.makedirs(emoji_dir, exist_ok=True)
|
||||
|
||||
# 获取所有支持的图片文件
|
||||
files_to_process = [f for f in os.listdir(emoji_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif'))]
|
||||
|
||||
files_to_process = [f for f in os.listdir(emoji_dir) if
|
||||
f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif'))]
|
||||
|
||||
for filename in files_to_process:
|
||||
image_path = os.path.join(emoji_dir, filename)
|
||||
|
||||
@@ -273,10 +277,14 @@ class EmojiManager:
|
||||
if '是' not in check:
|
||||
os.remove(image_path)
|
||||
logger.info(f"描述: {description}")
|
||||
logger.info(f"描述: {description}")
|
||||
logger.info(f"其不满足过滤规则,被剔除 {check}")
|
||||
continue
|
||||
logger.info(f"check通过 {check}")
|
||||
|
||||
if description is not None:
|
||||
embedding = await get_embedding(description)
|
||||
|
||||
if description is not None:
|
||||
embedding = await get_embedding(description)
|
||||
# 准备数据库记录
|
||||
@@ -312,19 +320,17 @@ class EmojiManager:
|
||||
logger.success(f"同步保存到images集合: {filename}")
|
||||
else:
|
||||
logger.warning(f"跳过表情包: {filename}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"扫描表情包失败: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
except Exception:
|
||||
logger.exception("扫描表情包失败")
|
||||
|
||||
async def _periodic_scan(self, interval_MINS: int = 10):
|
||||
"""定期扫描新表情包"""
|
||||
while True:
|
||||
print("\033[1;36m[表情包]\033[0m 开始扫描新表情包...")
|
||||
logger.info("开始扫描新表情包...")
|
||||
await self.scan_new_emojis()
|
||||
await asyncio.sleep(interval_MINS * 60) # 每600秒扫描一次
|
||||
|
||||
|
||||
def check_emoji_file_integrity(self):
|
||||
"""检查表情包文件完整性
|
||||
如果文件已被删除,则从数据库中移除对应记录
|
||||
@@ -335,7 +341,7 @@ class EmojiManager:
|
||||
all_emojis = list(self.db.db.emoji.find())
|
||||
removed_count = 0
|
||||
total_count = len(all_emojis)
|
||||
|
||||
|
||||
for emoji in all_emojis:
|
||||
try:
|
||||
if 'path' not in emoji:
|
||||
@@ -343,27 +349,27 @@ class EmojiManager:
|
||||
self.db.db.emoji.delete_one({'_id': emoji['_id']})
|
||||
removed_count += 1
|
||||
continue
|
||||
|
||||
|
||||
if 'embedding' not in emoji:
|
||||
logger.warning(f"发现过时记录(缺少embedding字段),ID: {emoji.get('_id', 'unknown')}")
|
||||
self.db.db.emoji.delete_one({'_id': emoji['_id']})
|
||||
removed_count += 1
|
||||
continue
|
||||
|
||||
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(emoji['path']):
|
||||
logger.warning(f"表情包文件已被删除: {emoji['path']}")
|
||||
# 从数据库中删除记录
|
||||
result = self.db.db.emoji.delete_one({'_id': emoji['_id']})
|
||||
if result.deleted_count > 0:
|
||||
logger.success(f"成功删除数据库记录: {emoji['_id']}")
|
||||
logger.debug(f"成功删除数据库记录: {emoji['_id']}")
|
||||
removed_count += 1
|
||||
else:
|
||||
logger.error(f"删除数据库记录失败: {emoji['_id']}")
|
||||
except Exception as item_error:
|
||||
logger.error(f"处理表情包记录时出错: {str(item_error)}")
|
||||
continue
|
||||
|
||||
|
||||
# 验证清理结果
|
||||
remaining_count = self.db.db.emoji.count_documents({})
|
||||
if removed_count > 0:
|
||||
@@ -371,7 +377,7 @@ class EmojiManager:
|
||||
logger.info(f"清理前总数: {total_count} | 清理后总数: {remaining_count}")
|
||||
else:
|
||||
logger.info(f"已检查 {total_count} 个表情包记录")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"检查表情包完整性失败: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -382,6 +388,6 @@ class EmojiManager:
|
||||
await asyncio.sleep(interval_MINS * 60)
|
||||
|
||||
|
||||
|
||||
# 创建全局单例
|
||||
emoji_manager = EmojiManager()
|
||||
emoji_manager = EmojiManager()
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import time
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
from nonebot import get_driver
|
||||
from loguru import logger
|
||||
|
||||
from ...common.database import Database
|
||||
from ..models.utils_model import LLM_request
|
||||
@@ -55,9 +56,7 @@ class ResponseGenerator:
|
||||
self.current_model_type = "r1_distill"
|
||||
current_model = self.model_r1_distill
|
||||
|
||||
print(
|
||||
f"+++++++++++++++++{global_config.BOT_NICKNAME}{self.current_model_type}思考中+++++++++++++++++"
|
||||
)
|
||||
logger.info(f"{global_config.BOT_NICKNAME}{self.current_model_type}思考中")
|
||||
|
||||
model_response = await self._generate_response_with_model(
|
||||
message, current_model
|
||||
@@ -65,7 +64,7 @@ class ResponseGenerator:
|
||||
raw_content = model_response
|
||||
|
||||
if model_response:
|
||||
print(f"{global_config.BOT_NICKNAME}的回复是:{model_response}")
|
||||
logger.info(f'{global_config.BOT_NICKNAME}的回复是:{model_response}')
|
||||
model_response = await self._process_response(model_response)
|
||||
if model_response:
|
||||
return model_response, raw_content
|
||||
@@ -122,8 +121,8 @@ class ResponseGenerator:
|
||||
# 生成回复
|
||||
try:
|
||||
content, reasoning_content = await model.generate_response(prompt)
|
||||
except Exception as e:
|
||||
print(f"生成回复时出错: {e}")
|
||||
except Exception:
|
||||
logger.exception("生成回复时出错")
|
||||
return None
|
||||
|
||||
# 保存到数据库
|
||||
@@ -219,7 +218,7 @@ class InitiativeMessageGenerate:
|
||||
prompt_builder._build_initiative_prompt_select(message.group_id)
|
||||
)
|
||||
content_select, reasoning = self.model_v3.generate_response(topic_select_prompt)
|
||||
print(f"[DEBUG] {content_select} {reasoning}")
|
||||
logger.debug(f"{content_select} {reasoning}")
|
||||
topics_list = [dot[0] for dot in dots_for_select]
|
||||
if content_select:
|
||||
if content_select in topics_list:
|
||||
@@ -232,12 +231,12 @@ class InitiativeMessageGenerate:
|
||||
select_dot[1], prompt_template
|
||||
)
|
||||
content_check, reasoning_check = self.model_v3.generate_response(prompt_check)
|
||||
print(f"[DEBUG] {content_check} {reasoning_check}")
|
||||
logger.info(f"{content_check} {reasoning_check}")
|
||||
if "yes" not in content_check.lower():
|
||||
return None
|
||||
prompt = prompt_builder._build_initiative_prompt(
|
||||
select_dot, prompt_template, memory
|
||||
)
|
||||
content, reasoning = self.model_r1.generate_response_async(prompt)
|
||||
print(f"[DEBUG] {content} {reasoning}")
|
||||
logger.debug(f"[DEBUG] {content} {reasoning}")
|
||||
return content
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import time
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
|
||||
from .cq_code import cq_code_tool
|
||||
@@ -14,11 +15,12 @@ from .chat_stream import chat_manager
|
||||
|
||||
class Message_Sender:
|
||||
"""发送器"""
|
||||
|
||||
def __init__(self):
|
||||
self.message_interval = (0.5, 1) # 消息间隔时间范围(秒)
|
||||
self.last_send_time = 0
|
||||
self._current_bot = None
|
||||
|
||||
|
||||
def set_bot(self, bot: Bot):
|
||||
"""设置当前bot实例"""
|
||||
self._current_bot = bot
|
||||
@@ -41,10 +43,10 @@ class Message_Sender:
|
||||
message=message_send.raw_message,
|
||||
auto_escape=False
|
||||
)
|
||||
print(f"\033[1;34m[调试]\033[0m 发送消息{message.processed_plain_text}成功")
|
||||
logger.success(f"[调试] 发送消息{message.processed_plain_text}成功")
|
||||
except Exception as e:
|
||||
print(f"发生错误 {e}")
|
||||
print(f"\033[1;34m[调试]\033[0m 发送消息{message.processed_plain_text}失败")
|
||||
logger.error(f"[调试] 发生错误 {e}")
|
||||
logger.error(f"[调试] 发送消息{message.processed_plain_text}失败")
|
||||
else:
|
||||
try:
|
||||
await self._current_bot.send_private_msg(
|
||||
@@ -52,10 +54,10 @@ class Message_Sender:
|
||||
message=message_send.raw_message,
|
||||
auto_escape=False
|
||||
)
|
||||
print(f"\033[1;34m[调试]\033[0m 发送消息{message.processed_plain_text}成功")
|
||||
logger.success(f"[调试] 发送消息{message.processed_plain_text}成功")
|
||||
except Exception as e:
|
||||
print(f"发生错误 {e}")
|
||||
print(f"\033[1;34m[调试]\033[0m 发送消息{message.processed_plain_text}失败")
|
||||
logger.error(f"发生错误 {e}")
|
||||
logger.error(f"[调试] 发送消息{message.processed_plain_text}失败")
|
||||
|
||||
|
||||
class MessageContainer:
|
||||
@@ -71,15 +73,15 @@ class MessageContainer:
|
||||
"""获取所有超时的Message_Sending对象(思考时间超过30秒),按thinking_start_time排序"""
|
||||
current_time = time.time()
|
||||
timeout_messages = []
|
||||
|
||||
|
||||
for msg in self.messages:
|
||||
if isinstance(msg, MessageSending):
|
||||
if current_time - msg.thinking_start_time > self.thinking_timeout:
|
||||
timeout_messages.append(msg)
|
||||
|
||||
|
||||
# 按thinking_start_time排序,时间早的在前面
|
||||
timeout_messages.sort(key=lambda x: x.thinking_start_time)
|
||||
|
||||
|
||||
return timeout_messages
|
||||
|
||||
def get_earliest_message(self) -> Optional[Union[MessageThinking, MessageSending]]:
|
||||
@@ -88,11 +90,11 @@ class MessageContainer:
|
||||
return None
|
||||
earliest_time = float('inf')
|
||||
earliest_message = None
|
||||
for msg in self.messages:
|
||||
for msg in self.messages:
|
||||
msg_time = msg.thinking_start_time
|
||||
if msg_time < earliest_time:
|
||||
earliest_time = msg_time
|
||||
earliest_message = msg
|
||||
earliest_message = msg
|
||||
return earliest_message
|
||||
|
||||
def add_message(self, message: Union[MessageThinking, MessageSending]) -> None:
|
||||
@@ -110,10 +112,10 @@ class MessageContainer:
|
||||
self.messages.remove(message)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"\033[1;31m[错误]\033[0m 移除消息时发生错误: {e}")
|
||||
except Exception:
|
||||
logger.exception("移除消息时发生错误")
|
||||
return False
|
||||
|
||||
|
||||
def has_messages(self) -> bool:
|
||||
"""检查是否有待发送的消息"""
|
||||
return bool(self.messages)
|
||||
@@ -121,7 +123,7 @@ class MessageContainer:
|
||||
def get_all_messages(self) -> List[Union[MessageSending, MessageThinking]]:
|
||||
"""获取所有消息"""
|
||||
return list(self.messages)
|
||||
|
||||
|
||||
|
||||
class MessageManager:
|
||||
"""管理所有聊天流的消息容器"""
|
||||
@@ -152,11 +154,11 @@ class MessageManager:
|
||||
if isinstance(message_earliest, MessageThinking):
|
||||
message_earliest.update_thinking_time()
|
||||
thinking_time = message_earliest.thinking_time
|
||||
print(f"\033[1;34m[调试]\033[0m 消息正在思考中,已思考{int(thinking_time)}秒\033[K\r", end='', flush=True)
|
||||
|
||||
print(f"消息正在思考中,已思考{int(thinking_time)}秒\r", end='', flush=True)
|
||||
|
||||
# 检查是否超时
|
||||
if thinking_time > global_config.thinking_timeout:
|
||||
print(f"\033[1;33m[警告]\033[0m 消息思考超时({thinking_time}秒),移除该消息")
|
||||
logger.warning(f"消息思考超时({thinking_time}秒),移除该消息")
|
||||
container.remove_message(message_earliest)
|
||||
else:
|
||||
print(f"\033[1;34m[调试]\033[0m 消息'{message_earliest.processed_plain_text}'正在发送中")
|
||||
@@ -174,7 +176,7 @@ class MessageManager:
|
||||
|
||||
message_timeout = container.get_timeout_messages()
|
||||
if message_timeout:
|
||||
print(f"\033[1;34m[调试]\033[0m 发现{len(message_timeout)}条超时消息")
|
||||
logger.warning(f"发现{len(message_timeout)}条超时消息")
|
||||
for msg in message_timeout:
|
||||
if msg == message_earliest:
|
||||
continue
|
||||
@@ -191,11 +193,11 @@ class MessageManager:
|
||||
await self.storage.store_message(msg,msg.chat_stream, None)
|
||||
|
||||
if not container.remove_message(msg):
|
||||
print("\033[1;33m[警告]\033[0m 尝试删除不存在的消息")
|
||||
except Exception as e:
|
||||
print(f"\033[1;31m[错误]\033[0m 处理超时消息时发生错误: {e}")
|
||||
logger.warning("尝试删除不存在的消息")
|
||||
except Exception:
|
||||
logger.exception("处理超时消息时发生错误")
|
||||
continue
|
||||
|
||||
|
||||
async def start_processor(self):
|
||||
"""启动消息处理器"""
|
||||
while self._running:
|
||||
@@ -206,6 +208,7 @@ class MessageManager:
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
# 创建全局消息管理器实例
|
||||
message_manager = MessageManager()
|
||||
# 创建全局发送器实例
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import random
|
||||
import time
|
||||
from typing import Optional
|
||||
from loguru import logger
|
||||
|
||||
from ...common.database import Database
|
||||
from ..memory_system.memory import hippocampus, memory_graph
|
||||
@@ -34,44 +35,43 @@ class PromptBuilder:
|
||||
|
||||
Returns:
|
||||
str: 构建好的prompt
|
||||
"""
|
||||
#先禁用关系
|
||||
"""
|
||||
# 先禁用关系
|
||||
if 0 > 30:
|
||||
relation_prompt = "关系特别特别好,你很喜欢喜欢他"
|
||||
relation_prompt_2 = "热情发言或者回复"
|
||||
elif 0 <-20:
|
||||
elif 0 < -20:
|
||||
relation_prompt = "关系很差,你很讨厌他"
|
||||
relation_prompt_2 = "骂他"
|
||||
else:
|
||||
relation_prompt = "关系一般"
|
||||
relation_prompt_2 = "发言或者回复"
|
||||
|
||||
#开始构建prompt
|
||||
|
||||
|
||||
#心情
|
||||
|
||||
# 开始构建prompt
|
||||
|
||||
# 心情
|
||||
mood_manager = MoodManager.get_instance()
|
||||
mood_prompt = mood_manager.get_prompt()
|
||||
|
||||
|
||||
#日程构建
|
||||
|
||||
# 日程构建
|
||||
current_date = time.strftime("%Y-%m-%d", time.localtime())
|
||||
current_time = time.strftime("%H:%M:%S", time.localtime())
|
||||
bot_schedule_now_time,bot_schedule_now_activity = bot_schedule.get_current_task()
|
||||
bot_schedule_now_time, bot_schedule_now_activity = bot_schedule.get_current_task()
|
||||
prompt_date = f'''今天是{current_date},现在是{current_time},你今天的日程是:\n{bot_schedule.today_schedule}\n你现在正在{bot_schedule_now_activity}\n'''
|
||||
|
||||
#知识构建
|
||||
# 知识构建
|
||||
start_time = time.time()
|
||||
|
||||
|
||||
prompt_info = ''
|
||||
promt_info_prompt = ''
|
||||
prompt_info = await self.get_prompt_info(message_txt,threshold=0.5)
|
||||
prompt_info = await self.get_prompt_info(message_txt, threshold=0.5)
|
||||
if prompt_info:
|
||||
prompt_info = f'''\n----------------------------------------------------\n你有以下这些[知识]:\n{prompt_info}\n请你记住上面的[知识],之后可能会用到\n----------------------------------------------------\n'''
|
||||
|
||||
prompt_info = f'''你有以下这些[知识]:{prompt_info}请你记住上面的[
|
||||
知识],之后可能会用到-'''
|
||||
|
||||
end_time = time.time()
|
||||
print(f"\033[1;32m[知识检索]\033[0m 耗时: {(end_time - start_time):.3f}秒")
|
||||
|
||||
logger.debug(f"知识检索耗时: {(end_time - start_time):.3f}秒")
|
||||
|
||||
# 获取聊天上下文
|
||||
chat_in_group=True
|
||||
chat_talking_prompt = ''
|
||||
@@ -90,7 +90,7 @@ class PromptBuilder:
|
||||
# 使用新的记忆获取方法
|
||||
memory_prompt = ''
|
||||
start_time = time.time()
|
||||
|
||||
|
||||
# 调用 hippocampus 的 get_relevant_memories 方法
|
||||
relevant_memories = await hippocampus.get_relevant_memories(
|
||||
text=message_txt,
|
||||
@@ -98,64 +98,64 @@ class PromptBuilder:
|
||||
similarity_threshold=0.4,
|
||||
max_memory_num=5
|
||||
)
|
||||
|
||||
|
||||
if relevant_memories:
|
||||
# 格式化记忆内容
|
||||
memory_items = []
|
||||
for memory in relevant_memories:
|
||||
memory_items.append(f"关于「{memory['topic']}」的记忆:{memory['content']}")
|
||||
|
||||
|
||||
memory_prompt = "看到这些聊天,你想起来:\n" + "\n".join(memory_items) + "\n"
|
||||
|
||||
|
||||
# 打印调试信息
|
||||
print("\n\033[1;32m[记忆检索]\033[0m 找到以下相关记忆:")
|
||||
logger.debug("[记忆检索]找到以下相关记忆:")
|
||||
for memory in relevant_memories:
|
||||
print(f"- 主题「{memory['topic']}」[相似度: {memory['similarity']:.2f}]: {memory['content']}")
|
||||
|
||||
logger.debug(f"- 主题「{memory['topic']}」[相似度: {memory['similarity']:.2f}]: {memory['content']}")
|
||||
|
||||
end_time = time.time()
|
||||
print(f"\033[1;32m[回忆耗时]\033[0m 耗时: {(end_time - start_time):.3f}秒")
|
||||
|
||||
|
||||
|
||||
#激活prompt构建
|
||||
logger.info(f"回忆耗时: {(end_time - start_time):.3f}秒")
|
||||
|
||||
# 激活prompt构建
|
||||
activate_prompt = ''
|
||||
if chat_in_group:
|
||||
activate_prompt = f"以上是群里正在进行的聊天,{memory_prompt} 现在昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和ta{relation_prompt},{mood_prompt},你想要{relation_prompt_2}。"
|
||||
else:
|
||||
activate_prompt = f"以上是你正在和{sender_name}私聊的内容,{memory_prompt} 现在昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和ta{relation_prompt},{mood_prompt},你想要{relation_prompt_2}。"
|
||||
#检测机器人相关词汇
|
||||
bot_keywords = ['人机', 'bot', '机器', '入机', 'robot', '机器人']
|
||||
is_bot = any(keyword in message_txt.lower() for keyword in bot_keywords)
|
||||
if is_bot:
|
||||
is_bot_prompt = '有人说你是人机或者机器人,否定这一事实,攻击或反问他。注意,只有认为你是机器人才回复,否则不要否认'
|
||||
else:
|
||||
is_bot_prompt = ''
|
||||
|
||||
# 关键词检测与反应
|
||||
keywords_reaction_prompt = ''
|
||||
for rule in global_config.keywords_reaction_rules:
|
||||
if rule.get("enable", False):
|
||||
if any(keyword in message_txt.lower() for keyword in rule.get("keywords", [])):
|
||||
logger.info(f"检测到以下关键词之一:{rule.get('keywords', [])},触发反应:{rule.get('reaction', '')}")
|
||||
keywords_reaction_prompt += rule.get("reaction", "") + ','
|
||||
|
||||
#人格选择
|
||||
personality=global_config.PROMPT_PERSONALITY
|
||||
probability_1 = global_config.PERSONALITY_1
|
||||
probability_2 = global_config.PERSONALITY_2
|
||||
probability_3 = global_config.PERSONALITY_3
|
||||
prompt_personality = ''
|
||||
|
||||
prompt_personality = f'{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},你还有很多别名:{"/".join(global_config.BOT_ALIAS_NAMES)},'
|
||||
personality_choice = random.random()
|
||||
if chat_in_group:
|
||||
prompt_in_group=f"你正在浏览{chat_stream.platform}群"
|
||||
else:
|
||||
prompt_in_group=f"你正在{chat_stream.platform}上和{sender_name}私聊"
|
||||
if personality_choice < probability_1: # 第一种人格
|
||||
prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[0]},{prompt_in_group},{promt_info_prompt},
|
||||
现在请你给出日常且口语化的回复,平淡一些,尽量简短一些。{is_bot_prompt}
|
||||
prompt_personality += f'''{personality[0]}, 你正在浏览qq群,{promt_info_prompt},
|
||||
现在请你给出日常且口语化的回复,平淡一些,尽量简短一些。{keywords_reaction_prompt}
|
||||
请注意把握群里的聊天内容,不要刻意突出自身学科背景,不要回复的太有条理,可以有个性。'''
|
||||
elif personality_choice < probability_1 + probability_2: # 第二种人格
|
||||
prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[1]},{prompt_in_group},{promt_info_prompt},
|
||||
现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{is_bot_prompt}
|
||||
prompt_personality += f'''{personality[1]}, 你正在浏览qq群,{promt_info_prompt},
|
||||
现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{keywords_reaction_prompt}
|
||||
请你表达自己的见解和观点。可以有个性。'''
|
||||
else: # 第三种人格
|
||||
prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[2]},{prompt_in_group},{promt_info_prompt},
|
||||
现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{is_bot_prompt}
|
||||
prompt_personality += f'''{personality[2]}, 你正在浏览qq群,{promt_info_prompt},
|
||||
现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{keywords_reaction_prompt}
|
||||
请你表达自己的见解和观点。可以有个性。'''
|
||||
|
||||
#中文高手(新加的好玩功能)
|
||||
|
||||
# 中文高手(新加的好玩功能)
|
||||
prompt_ger = ''
|
||||
if random.random() < 0.04:
|
||||
prompt_ger += '你喜欢用倒装句'
|
||||
@@ -163,23 +163,23 @@ class PromptBuilder:
|
||||
prompt_ger += '你喜欢用反问句'
|
||||
if random.random() < 0.01:
|
||||
prompt_ger += '你喜欢用文言文'
|
||||
|
||||
#额外信息要求
|
||||
extra_info = '''但是记得回复平淡一些,简短一些,尤其注意在没明确提到时不要过多提及自身的背景, 不要直接回复别人发的表情包,记住不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只需要输出回复内容就好,不要输出其他任何内容'''
|
||||
|
||||
#合并prompt
|
||||
|
||||
# 额外信息要求
|
||||
extra_info = '''但是记得回复平淡一些,简短一些,尤其注意在没明确提到时不要过多提及自身的背景, 不要直接回复别人发的表情包,记住不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只需要输出回复内容就好,不要输出其他任何内容'''
|
||||
|
||||
# 合并prompt
|
||||
prompt = ""
|
||||
prompt += f"{prompt_info}\n"
|
||||
prompt += f"{prompt_date}\n"
|
||||
prompt += f"{chat_talking_prompt}\n"
|
||||
prompt += f"{chat_talking_prompt}\n"
|
||||
prompt += f"{prompt_personality}\n"
|
||||
prompt += f"{prompt_ger}\n"
|
||||
prompt += f"{extra_info}\n"
|
||||
|
||||
'''读空气prompt处理'''
|
||||
activate_prompt_check=f"以上是群里正在进行的聊天,昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和他{relation_prompt},你想要{relation_prompt_2},但是这不一定是合适的时机,请你决定是否要回应这条消息。"
|
||||
prompt += f"{extra_info}\n"
|
||||
|
||||
'''读空气prompt处理'''
|
||||
activate_prompt_check = f"以上是群里正在进行的聊天,昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和他{relation_prompt},你想要{relation_prompt_2},但是这不一定是合适的时机,请你决定是否要回应这条消息。"
|
||||
prompt_personality_check = ''
|
||||
extra_check_info=f"请注意把握群里的聊天内容的基础上,综合群内的氛围,例如,和{global_config.BOT_NICKNAME}相关的话题要积极回复,如果是at自己的消息一定要回复,如果自己正在和别人聊天一定要回复,其他话题如果合适搭话也可以回复,如果认为应该回复请输出yes,否则输出no,请注意是决定是否需要回复,而不是编写回复内容,除了yes和no不要输出任何回复内容。"
|
||||
extra_check_info = f"请注意把握群里的聊天内容的基础上,综合群内的氛围,例如,和{global_config.BOT_NICKNAME}相关的话题要积极回复,如果是at自己的消息一定要回复,如果自己正在和别人聊天一定要回复,其他话题如果合适搭话也可以回复,如果认为应该回复请输出yes,否则输出no,请注意是决定是否需要回复,而不是编写回复内容,除了yes和no不要输出任何回复内容。"
|
||||
if personality_choice < probability_1: # 第一种人格
|
||||
prompt_personality_check = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[0]}, 你正在浏览qq群,{promt_info_prompt} {activate_prompt_check} {extra_check_info}'''
|
||||
elif personality_choice < probability_1 + probability_2: # 第二种人格
|
||||
@@ -187,34 +187,36 @@ class PromptBuilder:
|
||||
else: # 第三种人格
|
||||
prompt_personality_check = f'''你的网名叫{global_config.BOT_NICKNAME},{personality[2]}, 你正在浏览qq群,{promt_info_prompt} {activate_prompt_check} {extra_check_info}'''
|
||||
|
||||
prompt_check_if_response=f"{prompt_info}\n{prompt_date}\n{chat_talking_prompt}\n{prompt_personality_check}"
|
||||
|
||||
return prompt,prompt_check_if_response
|
||||
|
||||
def _build_initiative_prompt_select(self,group_id):
|
||||
prompt_check_if_response = f"{prompt_info}\n{prompt_date}\n{chat_talking_prompt}\n{prompt_personality_check}"
|
||||
|
||||
return prompt, prompt_check_if_response
|
||||
|
||||
def _build_initiative_prompt_select(self, group_id, probability_1=0.8, probability_2=0.1):
|
||||
current_date = time.strftime("%Y-%m-%d", time.localtime())
|
||||
current_time = time.strftime("%H:%M:%S", time.localtime())
|
||||
bot_schedule_now_time,bot_schedule_now_activity = bot_schedule.get_current_task()
|
||||
bot_schedule_now_time, bot_schedule_now_activity = bot_schedule.get_current_task()
|
||||
prompt_date = f'''今天是{current_date},现在是{current_time},你今天的日程是:\n{bot_schedule.today_schedule}\n你现在正在{bot_schedule_now_activity}\n'''
|
||||
|
||||
chat_talking_prompt = ''
|
||||
if group_id:
|
||||
chat_talking_prompt = get_recent_group_detailed_plain_text(self.db, group_id, limit=global_config.MAX_CONTEXT_SIZE,combine = True)
|
||||
|
||||
chat_talking_prompt = get_recent_group_detailed_plain_text(self.db, group_id,
|
||||
limit=global_config.MAX_CONTEXT_SIZE,
|
||||
combine=True)
|
||||
|
||||
chat_talking_prompt = f"以下是群里正在聊天的内容:\n{chat_talking_prompt}"
|
||||
# print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的消息记录:{chat_talking_prompt}")
|
||||
# print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的消息记录:{chat_talking_prompt}")
|
||||
|
||||
# 获取主动发言的话题
|
||||
all_nodes=memory_graph.dots
|
||||
all_nodes=filter(lambda dot:len(dot[1]['memory_items'])>3,all_nodes)
|
||||
nodes_for_select=random.sample(all_nodes,5)
|
||||
topics=[info[0] for info in nodes_for_select]
|
||||
infos=[info[1] for info in nodes_for_select]
|
||||
all_nodes = memory_graph.dots
|
||||
all_nodes = filter(lambda dot: len(dot[1]['memory_items']) > 3, all_nodes)
|
||||
nodes_for_select = random.sample(all_nodes, 5)
|
||||
topics = [info[0] for info in nodes_for_select]
|
||||
infos = [info[1] for info in nodes_for_select]
|
||||
|
||||
#激活prompt构建
|
||||
# 激活prompt构建
|
||||
activate_prompt = ''
|
||||
activate_prompt = "以上是群里正在进行的聊天。"
|
||||
personality=global_config.PROMPT_PERSONALITY
|
||||
personality = global_config.PROMPT_PERSONALITY
|
||||
prompt_personality = ''
|
||||
personality_choice = random.random()
|
||||
if personality_choice < probability_1: # 第一种人格
|
||||
@@ -223,32 +225,31 @@ class PromptBuilder:
|
||||
prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[1]}'''
|
||||
else: # 第三种人格
|
||||
prompt_personality = f'''{activate_prompt}你的网名叫{global_config.BOT_NICKNAME},{personality[2]}'''
|
||||
|
||||
topics_str=','.join(f"\"{topics}\"")
|
||||
prompt_for_select=f"你现在想在群里发言,回忆了一下,想到几个话题,分别是{topics_str},综合当前状态以及群内气氛,请你在其中选择一个合适的话题,注意只需要输出话题,除了话题什么也不要输出(双引号也不要输出)"
|
||||
|
||||
prompt_initiative_select=f"{prompt_date}\n{prompt_personality}\n{prompt_for_select}"
|
||||
prompt_regular=f"{prompt_date}\n{prompt_personality}"
|
||||
|
||||
return prompt_initiative_select,nodes_for_select,prompt_regular
|
||||
|
||||
def _build_initiative_prompt_check(self,selected_node,prompt_regular):
|
||||
memory=random.sample(selected_node['memory_items'],3)
|
||||
memory='\n'.join(memory)
|
||||
prompt_for_check=f"{prompt_regular}你现在想在群里发言,回忆了一下,想到一个话题,是{selected_node['concept']},关于这个话题的记忆有\n{memory}\n,以这个作为主题发言合适吗?请在把握群里的聊天内容的基础上,综合群内的氛围,如果认为应该发言请输出yes,否则输出no,请注意是决定是否需要发言,而不是编写回复内容,除了yes和no不要输出任何回复内容。"
|
||||
return prompt_for_check,memory
|
||||
|
||||
def _build_initiative_prompt(self,selected_node,prompt_regular,memory):
|
||||
prompt_for_initiative=f"{prompt_regular}你现在想在群里发言,回忆了一下,想到一个话题,是{selected_node['concept']},关于这个话题的记忆有\n{memory}\n,请在把握群里的聊天内容的基础上,综合群内的氛围,以日常且口语化的口吻,简短且随意一点进行发言,不要说的太有条理,可以有个性。记住不要输出多余内容(包括前后缀,冒号和引号,括号,表情等)"
|
||||
topics_str = ','.join(f"\"{topics}\"")
|
||||
prompt_for_select = f"你现在想在群里发言,回忆了一下,想到几个话题,分别是{topics_str},综合当前状态以及群内气氛,请你在其中选择一个合适的话题,注意只需要输出话题,除了话题什么也不要输出(双引号也不要输出)"
|
||||
|
||||
prompt_initiative_select = f"{prompt_date}\n{prompt_personality}\n{prompt_for_select}"
|
||||
prompt_regular = f"{prompt_date}\n{prompt_personality}"
|
||||
|
||||
return prompt_initiative_select, nodes_for_select, prompt_regular
|
||||
|
||||
def _build_initiative_prompt_check(self, selected_node, prompt_regular):
|
||||
memory = random.sample(selected_node['memory_items'], 3)
|
||||
memory = '\n'.join(memory)
|
||||
prompt_for_check = f"{prompt_regular}你现在想在群里发言,回忆了一下,想到一个话题,是{selected_node['concept']},关于这个话题的记忆有\n{memory}\n,以这个作为主题发言合适吗?请在把握群里的聊天内容的基础上,综合群内的氛围,如果认为应该发言请输出yes,否则输出no,请注意是决定是否需要发言,而不是编写回复内容,除了yes和no不要输出任何回复内容。"
|
||||
return prompt_for_check, memory
|
||||
|
||||
def _build_initiative_prompt(self, selected_node, prompt_regular, memory):
|
||||
prompt_for_initiative = f"{prompt_regular}你现在想在群里发言,回忆了一下,想到一个话题,是{selected_node['concept']},关于这个话题的记忆有\n{memory}\n,请在把握群里的聊天内容的基础上,综合群内的氛围,以日常且口语化的口吻,简短且随意一点进行发言,不要说的太有条理,可以有个性。记住不要输出多余内容(包括前后缀,冒号和引号,括号,表情等)"
|
||||
return prompt_for_initiative
|
||||
|
||||
|
||||
async def get_prompt_info(self,message:str,threshold:float):
|
||||
async def get_prompt_info(self, message: str, threshold: float):
|
||||
related_info = ''
|
||||
print(f"\033[1;34m[调试]\033[0m 获取知识库内容,元消息:{message[:30]}...,消息长度: {len(message)}")
|
||||
logger.debug(f"获取知识库内容,元消息:{message[:30]}...,消息长度: {len(message)}")
|
||||
embedding = await get_embedding(message)
|
||||
related_info += self.get_info_from_db(embedding,threshold=threshold)
|
||||
|
||||
related_info += self.get_info_from_db(embedding, threshold=threshold)
|
||||
|
||||
return related_info
|
||||
|
||||
def get_info_from_db(self, query_embedding: list, limit: int = 1, threshold: float = 0.5) -> str:
|
||||
@@ -309,14 +310,15 @@ class PromptBuilder:
|
||||
{"$limit": limit},
|
||||
{"$project": {"content": 1, "similarity": 1}}
|
||||
]
|
||||
|
||||
|
||||
results = list(self.db.db.knowledges.aggregate(pipeline))
|
||||
# print(f"\033[1;34m[调试]\033[0m获取知识库内容结果: {results}")
|
||||
|
||||
|
||||
if not results:
|
||||
return ''
|
||||
|
||||
|
||||
# 返回所有找到的内容,用换行分隔
|
||||
return '\n'.join(str(result['content']) for result in results)
|
||||
|
||||
prompt_builder = PromptBuilder()
|
||||
|
||||
|
||||
prompt_builder = PromptBuilder()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
from typing import Optional, Union
|
||||
from typing import Optional, Union
|
||||
from loguru import logger
|
||||
|
||||
from ...common.database import Database
|
||||
from .message_base import UserInfo
|
||||
@@ -10,9 +11,10 @@ class Impression:
|
||||
traits: str = None
|
||||
called: str = None
|
||||
know_time: float = None
|
||||
|
||||
|
||||
relationship_value: float = None
|
||||
|
||||
|
||||
class Relationship:
|
||||
user_id: int = None
|
||||
platform: str = None
|
||||
@@ -79,7 +81,7 @@ class RelationshipManager:
|
||||
# 保存到数据库
|
||||
await self.storage_relationship(relationship)
|
||||
relationship.saved = True
|
||||
|
||||
|
||||
return relationship
|
||||
|
||||
async def update_relationship_value(self,
|
||||
@@ -121,7 +123,7 @@ class RelationshipManager:
|
||||
# 如果不存在且提供了user_info,则创建新的关系
|
||||
if user_info is not None:
|
||||
return await self.update_relationship(chat_stream=chat_stream, **kwargs)
|
||||
print(f"\033[1;31m[关系管理]\033[0m 用户 {user_id}({platform}) 不存在,无法更新")
|
||||
logger.warning(f"[关系管理] 用户 {user_id}({platform}) 不存在,无法更新")
|
||||
return None
|
||||
|
||||
def get_relationship(self,
|
||||
@@ -151,7 +153,7 @@ class RelationshipManager:
|
||||
return self.relationships[key]
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
async def load_relationship(self, data: dict) -> Relationship:
|
||||
"""从数据库加载或创建新的关系对象"""
|
||||
# 确保data中有platform字段,如果没有则默认为'qq'
|
||||
@@ -163,14 +165,14 @@ class RelationshipManager:
|
||||
key = (rela.user_id, rela.platform)
|
||||
self.relationships[key] = rela
|
||||
return rela
|
||||
|
||||
|
||||
async def load_all_relationships(self):
|
||||
"""加载所有关系对象"""
|
||||
db = Database.get_instance()
|
||||
all_relationships = db.db.relationships.find({})
|
||||
for data in all_relationships:
|
||||
await self.load_relationship(data)
|
||||
|
||||
|
||||
async def _start_relationship_manager(self):
|
||||
"""每5分钟自动保存一次关系数据"""
|
||||
db = Database.get_instance()
|
||||
@@ -179,15 +181,15 @@ class RelationshipManager:
|
||||
# 依次加载每条记录
|
||||
for data in all_relationships:
|
||||
await self.load_relationship(data)
|
||||
print(f"\033[1;32m[关系管理]\033[0m 已加载 {len(self.relationships)} 条关系记录")
|
||||
logger.debug(f"[关系管理] 已加载 {len(self.relationships)} 条关系记录")
|
||||
|
||||
while True:
|
||||
print("\033[1;32m[关系管理]\033[0m 正在自动保存关系")
|
||||
logger.debug("正在自动保存关系")
|
||||
await asyncio.sleep(300) # 等待300秒(5分钟)
|
||||
await self._save_all_relationships()
|
||||
|
||||
|
||||
async def _save_all_relationships(self):
|
||||
"""将所有关系数据保存到数据库"""
|
||||
"""将所有关系数据保存到数据库"""
|
||||
# 保存所有关系数据
|
||||
for (userid, platform), relationship in self.relationships.items():
|
||||
if not relationship.saved:
|
||||
@@ -203,7 +205,7 @@ class RelationshipManager:
|
||||
gender = relationship.gender
|
||||
age = relationship.age
|
||||
saved = relationship.saved
|
||||
|
||||
|
||||
db = Database.get_instance()
|
||||
db.db.relationships.update_one(
|
||||
{'user_id': user_id, 'platform': platform},
|
||||
@@ -252,4 +254,4 @@ class RelationshipManager:
|
||||
return "某人"
|
||||
|
||||
|
||||
relationship_manager = RelationshipManager()
|
||||
relationship_manager = RelationshipManager()
|
||||
|
||||
@@ -5,6 +5,8 @@ from ...common.database import Database
|
||||
from .message_base import MessageBase
|
||||
from .message import MessageSending, MessageRecv
|
||||
from .chat_stream import ChatStream
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class MessageStorage:
|
||||
def __init__(self):
|
||||
@@ -24,7 +26,7 @@ class MessageStorage:
|
||||
"topic": topic,
|
||||
}
|
||||
self.db.db.messages.insert_one(message_data)
|
||||
except Exception as e:
|
||||
print(f"\033[1;31m[错误]\033[0m 存储消息失败: {e}")
|
||||
except Exception:
|
||||
logger.exception("存储消息失败")
|
||||
|
||||
# 如果需要其他存储相关的函数,可以在这里添加
|
||||
# 如果需要其他存储相关的函数,可以在这里添加
|
||||
|
||||
@@ -4,9 +4,11 @@ from nonebot import get_driver
|
||||
|
||||
from ..models.utils_model import LLM_request
|
||||
from .config import global_config
|
||||
from loguru import logger
|
||||
|
||||
driver = get_driver()
|
||||
config = driver.config
|
||||
config = driver.config
|
||||
|
||||
|
||||
class TopicIdentifier:
|
||||
def __init__(self):
|
||||
@@ -23,19 +25,20 @@ class TopicIdentifier:
|
||||
|
||||
# 使用 LLM_request 类进行请求
|
||||
topic, _ = await self.llm_topic_judge.generate_response(prompt)
|
||||
|
||||
|
||||
if not topic:
|
||||
print("\033[1;31m[错误]\033[0m LLM API 返回为空")
|
||||
logger.error("LLM API 返回为空")
|
||||
return None
|
||||
|
||||
|
||||
# 直接在这里处理主题解析
|
||||
if not topic or topic == "无主题":
|
||||
return None
|
||||
|
||||
|
||||
# 解析主题字符串为列表
|
||||
topic_list = [t.strip() for t in topic.split(",") if t.strip()]
|
||||
|
||||
print(f"\033[1;32m[主题识别]\033[0m 主题: {topic_list}")
|
||||
|
||||
logger.info(f"主题: {topic_list}")
|
||||
return topic_list if topic_list else None
|
||||
|
||||
topic_identifier = TopicIdentifier()
|
||||
|
||||
topic_identifier = TopicIdentifier()
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Dict, List
|
||||
import jieba
|
||||
import numpy as np
|
||||
from nonebot import get_driver
|
||||
from loguru import logger
|
||||
|
||||
from ..models.utils_model import LLM_request
|
||||
from ..utils.typo_generator import ChineseTypoGenerator
|
||||
@@ -21,16 +22,16 @@ config = driver.config
|
||||
|
||||
|
||||
def db_message_to_str(message_dict: Dict) -> str:
|
||||
print(f"message_dict: {message_dict}")
|
||||
logger.debug(f"message_dict: {message_dict}")
|
||||
time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(message_dict["time"]))
|
||||
try:
|
||||
name = "[(%s)%s]%s" % (
|
||||
message_dict['user_id'], message_dict.get("user_nickname", ""), message_dict.get("user_cardname", ""))
|
||||
message_dict['user_id'], message_dict.get("user_nickname", ""), message_dict.get("user_cardname", ""))
|
||||
except:
|
||||
name = message_dict.get("user_nickname", "") or f"用户{message_dict['user_id']}"
|
||||
content = message_dict.get("processed_plain_text", "")
|
||||
result = f"[{time_str}] {name}: {content}\n"
|
||||
print(f"result: {result}")
|
||||
logger.debug(f"result: {result}")
|
||||
return result
|
||||
|
||||
|
||||
@@ -71,37 +72,43 @@ def calculate_information_content(text):
|
||||
|
||||
|
||||
def get_cloest_chat_from_db(db, length: int, timestamp: str):
|
||||
"""从数据库中获取最接近指定时间戳的聊天记录,并记录读取次数"""
|
||||
chat_text = ''
|
||||
"""从数据库中获取最接近指定时间戳的聊天记录,并记录读取次数
|
||||
|
||||
Returns:
|
||||
list: 消息记录字典列表,每个字典包含消息内容和时间信息
|
||||
"""
|
||||
chat_records = []
|
||||
closest_record = db.db.messages.find_one({"time": {"$lte": timestamp}}, sort=[('time', -1)])
|
||||
|
||||
if closest_record and closest_record.get('memorized', 0) < 4:
|
||||
|
||||
if closest_record and closest_record.get('memorized', 0) < 4:
|
||||
closest_time = closest_record['time']
|
||||
chat_id = closest_record['chat_id'] # 获取groupid
|
||||
# 获取该时间戳之后的length条消息,且groupid相同
|
||||
chat_records = list(db.db.messages.find(
|
||||
{"time": {"$gt": closest_time}, "chat_id": chat_id}
|
||||
).sort('time', 1).limit(length))
|
||||
|
||||
|
||||
# 更新每条消息的memorized属性
|
||||
for record in chat_records:
|
||||
# 检查当前记录的memorized值
|
||||
for record in records:
|
||||
current_memorized = record.get('memorized', 0)
|
||||
if current_memorized > 3:
|
||||
# print(f"消息已读取3次,跳过")
|
||||
print("消息已读取3次,跳过")
|
||||
return ''
|
||||
|
||||
|
||||
# 更新memorized值
|
||||
db.db.messages.update_one(
|
||||
{"_id": record["_id"]},
|
||||
{"$set": {"memorized": current_memorized + 1}}
|
||||
)
|
||||
|
||||
chat_text += record["detailed_plain_text"]
|
||||
|
||||
return chat_text
|
||||
# print(f"消息已读取3次,跳过")
|
||||
return ''
|
||||
|
||||
# 添加到记录列表中
|
||||
chat_records.append({
|
||||
'text': record["detailed_plain_text"],
|
||||
'time': record["time"],
|
||||
'group_id': record["group_id"]
|
||||
})
|
||||
|
||||
return chat_records
|
||||
|
||||
|
||||
async def get_recent_group_messages(db, chat_id:str, limit: int = 12) -> list:
|
||||
@@ -142,7 +149,7 @@ async def get_recent_group_messages(db, chat_id:str, limit: int = 12) -> list:
|
||||
)
|
||||
message_objects.append(msg)
|
||||
except KeyError:
|
||||
print("[WARNING] 数据库中存在无效的消息")
|
||||
logger.warning("数据库中存在无效的消息")
|
||||
continue
|
||||
|
||||
# 按时间正序排列
|
||||
@@ -259,11 +266,10 @@ def split_into_sentences_w_remove_punctuation(text: str) -> List[str]:
|
||||
sentence = sentence.replace(',', ' ').replace(',', ' ')
|
||||
sentences_done.append(sentence)
|
||||
|
||||
print(f"处理后的句子: {sentences_done}")
|
||||
logger.info(f"处理后的句子: {sentences_done}")
|
||||
return sentences_done
|
||||
|
||||
|
||||
|
||||
def random_remove_punctuation(text: str) -> str:
|
||||
"""随机处理标点符号,模拟人类打字习惯
|
||||
|
||||
@@ -291,43 +297,70 @@ def random_remove_punctuation(text: str) -> str:
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def process_llm_response(text: str) -> List[str]:
|
||||
# processed_response = process_text_with_typos(content)
|
||||
if len(text) > 300:
|
||||
print(f"回复过长 ({len(text)} 字符),返回默认回复")
|
||||
if len(text) > 200:
|
||||
logger.warning(f"回复过长 ({len(text)} 字符),返回默认回复")
|
||||
return ['懒得说']
|
||||
# 处理长消息
|
||||
typo_generator = ChineseTypoGenerator(
|
||||
error_rate=0.03,
|
||||
min_freq=7,
|
||||
tone_error_rate=0.2,
|
||||
word_replace_rate=0.02
|
||||
error_rate=global_config.chinese_typo_error_rate,
|
||||
min_freq=global_config.chinese_typo_min_freq,
|
||||
tone_error_rate=global_config.chinese_typo_tone_error_rate,
|
||||
word_replace_rate=global_config.chinese_typo_word_replace_rate
|
||||
)
|
||||
typoed_text = typo_generator.create_typo_sentence(text)[0]
|
||||
sentences = split_into_sentences_w_remove_punctuation(typoed_text)
|
||||
split_sentences = split_into_sentences_w_remove_punctuation(text)
|
||||
sentences = []
|
||||
for sentence in split_sentences:
|
||||
if global_config.chinese_typo_enable:
|
||||
typoed_text, typo_corrections = typo_generator.create_typo_sentence(sentence)
|
||||
sentences.append(typoed_text)
|
||||
if typo_corrections:
|
||||
sentences.append(typo_corrections)
|
||||
else:
|
||||
sentences.append(sentence)
|
||||
# 检查分割后的消息数量是否过多(超过3条)
|
||||
if len(sentences) > 4:
|
||||
print(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复")
|
||||
|
||||
if len(sentences) > 5:
|
||||
logger.warning(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复")
|
||||
return [f'{global_config.BOT_NICKNAME}不知道哦']
|
||||
|
||||
return sentences
|
||||
|
||||
|
||||
def calculate_typing_time(input_string: str, chinese_time: float = 0.2, english_time: float = 0.1) -> float:
|
||||
def calculate_typing_time(input_string: str, chinese_time: float = 0.4, english_time: float = 0.2) -> float:
|
||||
"""
|
||||
计算输入字符串所需的时间,中文和英文字符有不同的输入时间
|
||||
input_string (str): 输入的字符串
|
||||
chinese_time (float): 中文字符的输入时间,默认为0.3秒
|
||||
english_time (float): 英文字符的输入时间,默认为0.15秒
|
||||
chinese_time (float): 中文字符的输入时间,默认为0.2秒
|
||||
english_time (float): 英文字符的输入时间,默认为0.1秒
|
||||
|
||||
特殊情况:
|
||||
- 如果只有一个中文字符,将使用3倍的中文输入时间
|
||||
- 在所有输入结束后,额外加上回车时间0.3秒
|
||||
"""
|
||||
mood_manager = MoodManager.get_instance()
|
||||
# 将0-1的唤醒度映射到-1到1
|
||||
mood_arousal = mood_manager.current_mood.arousal
|
||||
# 映射到0.5到2倍的速度系数
|
||||
typing_speed_multiplier = 1.5 ** mood_arousal # 唤醒度为1时速度翻倍,为-1时速度减半
|
||||
chinese_time *= 1 / typing_speed_multiplier
|
||||
english_time *= 1 / typing_speed_multiplier
|
||||
# 计算中文字符数
|
||||
chinese_chars = sum(1 for char in input_string if '\u4e00' <= char <= '\u9fff')
|
||||
|
||||
# 如果只有一个中文字符,使用3倍时间
|
||||
if chinese_chars == 1 and len(input_string.strip()) == 1:
|
||||
return chinese_time * 3 + 0.3 # 加上回车时间
|
||||
|
||||
# 正常计算所有字符的输入时间
|
||||
total_time = 0.0
|
||||
for char in input_string:
|
||||
if '\u4e00' <= char <= '\u9fff': # 判断是否为中文字符
|
||||
total_time += chinese_time
|
||||
else: # 其他字符(如英文)
|
||||
total_time += english_time
|
||||
return total_time
|
||||
return total_time + 0.3 # 加上回车时间
|
||||
|
||||
|
||||
def cosine_similarity(v1, v2):
|
||||
|
||||
@@ -16,7 +16,9 @@ class WillingManager:
|
||||
self.chat_reply_willing: Dict[str, float] = {} # 存储每个聊天流的回复意愿
|
||||
self._decay_task = None
|
||||
self._started = False
|
||||
|
||||
self.min_reply_willing = 0.01
|
||||
self.attenuation_coefficient = 0.75
|
||||
|
||||
async def _decay_reply_willing(self):
|
||||
"""定期衰减回复意愿"""
|
||||
while True:
|
||||
@@ -33,12 +35,9 @@ class WillingManager:
|
||||
return self.chat_reply_willing.get(stream.stream_id, 0)
|
||||
return 0
|
||||
|
||||
def set_willing(self, chat_id: str, willing: float):
|
||||
"""设置指定聊天流的回复意愿"""
|
||||
self.chat_reply_willing[chat_id] = willing
|
||||
def set_willing(self, chat_id: str, willing: float):
|
||||
"""设置指定聊天流的回复意愿"""
|
||||
self.chat_reply_willing[chat_id] = willing
|
||||
def set_willing(self, chat_id: int, willing: float):
|
||||
"""设置指定群组的回复意愿"""
|
||||
self.group_reply_willing[chat_id] = willing
|
||||
|
||||
async def change_reply_willing_received(self,
|
||||
chat_stream:ChatStream,
|
||||
@@ -51,47 +50,67 @@ class WillingManager:
|
||||
# 获取或创建聊天流
|
||||
stream = chat_stream
|
||||
chat_id = stream.stream_id
|
||||
group_id = stream.group_info.group_id
|
||||
|
||||
# 若非目标回复群组,则直接return
|
||||
if group_id not in config.talk_allowed_groups:
|
||||
reply_probability = 0
|
||||
return reply_probability
|
||||
|
||||
|
||||
current_willing = self.chat_reply_willing.get(chat_id, 0)
|
||||
|
||||
# print(f"初始意愿: {current_willing}")
|
||||
if is_mentioned_bot and current_willing < 1.0:
|
||||
current_willing += 0.9
|
||||
print(f"被提及, 当前意愿: {current_willing}")
|
||||
elif is_mentioned_bot:
|
||||
current_willing += 0.05
|
||||
print(f"被重复提及, 当前意愿: {current_willing}")
|
||||
|
||||
logger.debug(f"[{chat_id}]的初始回复意愿: {current_willing}")
|
||||
|
||||
|
||||
# 根据消息类型(被cue/表情包)调控
|
||||
if is_mentioned_bot:
|
||||
current_willing = min(
|
||||
3.0,
|
||||
current_willing + 0.9
|
||||
)
|
||||
logger.debug(f"被提及, 当前意愿: {current_willing}")
|
||||
|
||||
if is_emoji:
|
||||
current_willing *= 0.1
|
||||
print(f"表情包, 当前意愿: {current_willing}")
|
||||
|
||||
print(f"放大系数_interested_rate: {global_config.response_interested_rate_amplifier}")
|
||||
interested_rate *= global_config.response_interested_rate_amplifier #放大回复兴趣度
|
||||
if interested_rate > 0.4:
|
||||
# print(f"兴趣度: {interested_rate}, 当前意愿: {current_willing}")
|
||||
current_willing += interested_rate-0.4
|
||||
|
||||
current_willing *= global_config.response_willing_amplifier #放大回复意愿
|
||||
# print(f"放大系数_willing: {global_config.response_willing_amplifier}, 当前意愿: {current_willing}")
|
||||
|
||||
reply_probability = max((current_willing - 0.45) * 2, 0)
|
||||
|
||||
# 检查群组权限(如果是群聊)
|
||||
if chat_stream.group_info:
|
||||
if chat_stream.group_info.group_id not in config.talk_allowed_groups:
|
||||
current_willing = 0
|
||||
reply_probability = 0
|
||||
|
||||
if chat_stream.group_info.group_id in config.talk_frequency_down_groups:
|
||||
reply_probability = reply_probability / global_config.down_frequency_rate
|
||||
logger.debug(f"表情包, 当前意愿: {current_willing}")
|
||||
|
||||
# 兴趣放大系数,若兴趣 > 0.4则增加回复概率
|
||||
interested_rate_amplifier = global_config.response_interested_rate_amplifier
|
||||
logger.debug(f"放大系数_interested_rate: {interested_rate_amplifier}")
|
||||
interested_rate *= interested_rate_amplifier
|
||||
|
||||
current_willing += max(
|
||||
0.0,
|
||||
interested_rate - 0.4
|
||||
)
|
||||
|
||||
# 回复意愿系数调控,独立乘区
|
||||
willing_amplifier = max(
|
||||
global_config.response_willing_amplifier,
|
||||
self.min_reply_willing
|
||||
)
|
||||
current_willing *= willing_amplifier
|
||||
logger.debug(f"放大系数_willing: {global_config.response_willing_amplifier}, 当前意愿: {current_willing}")
|
||||
|
||||
# 回复概率迭代,保底0.01回复概率
|
||||
reply_probability = max(
|
||||
(current_willing - 0.45) * 2,
|
||||
self.min_reply_willing
|
||||
)
|
||||
|
||||
# 降低目标低频群组回复概率
|
||||
down_frequency_rate = max(
|
||||
1.0,
|
||||
global_config.down_frequency_rate
|
||||
)
|
||||
if group_id in config.talk_frequency_down_groups:
|
||||
reply_probability = reply_probability / down_frequency_rate
|
||||
|
||||
reply_probability = min(reply_probability, 1)
|
||||
if reply_probability < 0:
|
||||
reply_probability = 0
|
||||
|
||||
self.chat_reply_willing[chat_id] = min(current_willing, 3.0)
|
||||
self.chat_reply_willing[chat_id] = min(current_willing, 3.0)
|
||||
|
||||
self.group_reply_willing[group_id] = min(current_willing, 3.0)
|
||||
logger.debug(f"当前群组{group_id}回复概率:{reply_probability}")
|
||||
return reply_probability
|
||||
|
||||
def change_reply_willing_sent(self, chat_stream:ChatStream):
|
||||
@@ -116,5 +135,6 @@ class WillingManager:
|
||||
self._decay_task = asyncio.create_task(self._decay_reply_willing())
|
||||
self._started = True
|
||||
|
||||
|
||||
# 创建全局实例
|
||||
willing_manager = WillingManager()
|
||||
willing_manager = WillingManager()
|
||||
|
||||
Reference in New Issue
Block a user