v0.2修改了一些东西

使概率配置生效
将一些模块解耦合
将组信息管理器合并到关系管理器,添加了可以全局调用的接口
精简了llm生成器的代码
精简了message代码
重写了回复后处理
This commit is contained in:
SengokuCola
2025-02-28 00:28:34 +08:00
parent 6938a97941
commit 870be0a426
14 changed files with 333 additions and 653 deletions

View File

@@ -4,7 +4,7 @@
<div align="center">
![Python Version](https://img.shields.io/badge/Python-3.x-blue)
![License](https://img.shields.io/badge/license-MIT-green)
<!-- ![License](https://img.shields.io/badge/license-MIT-green) -->
![Status](https://img.shields.io/badge/状态-开发中-yellow)
</div>
@@ -15,7 +15,8 @@
基于napcat、nonebot和mongodb的专注于群聊天的qqbot
> ⚠️ **警告**代码可能随时更改目前版本不一定是稳定版本请自行了解qqbot的风险
> ⚠️ **警告**:代码可能随时更改,目前版本不一定是稳定版本,请自行了解qqbot的风险
> ⚠️ **警告**由于麦麦一直在迭代所以可能存在一些bug请自行测试包括胡言乱语
## 🚀 快速开始
@@ -44,7 +45,7 @@
## 🎯 功能介绍
### 💬 聊天功能
- 支持关键词检索主动发言对消息的话题topic进行识别如果检测到麦麦存储过的话题就会主动进行发言没写完
- 支持关键词检索主动发言对消息的话题topic进行识别如果检测到麦麦存储过的话题就会主动进行发言目前有bug
- 支持bot名字呼唤发言检测到"麦麦"会主动发言,可配置
- 使用硅基流动的api进行回复生成可随机使用R1V3R1-distill等模型未来将加入官网api支持
- 动态的prompt构建器更拟人
@@ -53,7 +54,7 @@
### 😊 表情包功能
- 支持根据发言内容发送对应情绪的表情包:未完善,可以用
- 会自动偷群友的表情包(未完善,暂时禁用)
- 会自动偷群友的表情包(未完善,暂时禁用)目前有bug
### 📅 日程功能
- 麦麦会自动生成一天的日程,实现更拟人的回复
@@ -64,11 +65,9 @@
### 📚 知识库功能
- 基于embedding模型的知识库手动放入txt会自动识别写完了暂时禁用
### 📊 可视化的界面
- 基于gradio的后台界面可以显示实时消息思考过程和消息发送流WIP
### 👥 关系功能
- 针对每个用户创建"关系"可以对不同用户进行个性化回复目前只有极其简单的好感度WIP
- 针对每个群创建"群印象"可以对不同群进行个性化回复WIP
## 🚧 开发中功能
- 人格功能WIP

View File

@@ -6,7 +6,6 @@ from .llm_generator import LLMResponseGenerator
from .message_stream import MessageStream, MessageStreamContainer
from .topic_identifier import topic_identifier
from random import random
from .group_info_manager import GroupInfoManager # 导入群信息管理器
from .emoji_manager import emoji_manager # 导入表情包管理器
import time
import os
@@ -15,14 +14,13 @@ from .message_send_control import message_sender # 导入消息发送控制器
from .message import Message_Thinking # 导入 Message_Thinking 类
from .relationship_manager import relationship_manager
from .willing_manager import willing_manager # 导入意愿管理器
from .utils import is_mentioned_bot_in_txt, calculate_typing_time
class ChatBot:
def __init__(self, config: BotConfig):
self.config = config
self.storage = MessageStorage()
self.gpt = LLMResponseGenerator(config)
self.group_info_manager = GroupInfoManager() # 初始化群信息管理器
self.bot = None # bot 实例引用
self._started = False
@@ -79,26 +77,19 @@ class ChatBot:
# print(f"- original_message: {event.reply.original_message}")
'''
# 获取群组信息,发送消息的用户信息,并对数据库内容做一次更新
group_info = await bot.get_group_info(group_id=event.group_id)
await self.group_info_manager.update_group_info(
group_id=event.group_id,
group_name=group_info['group_name'],
member_count=group_info['member_count']
)
sender_info = await bot.get_group_member_info(group_id=event.group_id, user_id=event.user_id, no_cache=True)
# print(f"\033[1;32m[关系管理]\033[0m 更新关系: {sender_info}")
await relationship_manager.update_relationship(user_id = event.user_id, data = sender_info)
await relationship_manager.update_relationship_value(user_id = event.user_id, relationship_value = 0.5)
print(f"\033[1;32m[关系管理]\033[0m 更新关系值: {relationship_manager.get_relationship(event.user_id).relationship_value}")
message = Message(
group_id=event.group_id,
user_id=event.user_id,
@@ -113,13 +104,12 @@ class ChatBot:
topic = topic_identifier.identify_topic_jieba(message.processed_plain_text)
print(f"\033[1;32m[主题识别]\033[0m 主题: {topic}")
await self.storage.store_message(message, topic[0] if topic else None)
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.time))
print(f"\033[1;34m[调试]\033[0m 当前消息是否是表情包: {message.is_emoji}")
is_mentioned = self.is_mentioned_bot(message)
is_mentioned = is_mentioned_bot_in_txt(message.processed_plain_text)
reply_probability = willing_manager.change_reply_willing_received(
event.group_id,
topic[0] if topic else None,
@@ -133,28 +123,31 @@ class ChatBot:
print(f"\033[1;32m[{current_time}][{message.group_name}]{message.user_nickname}:\033[0m {message.processed_plain_text}\033[1;36m[回复意愿:{current_willing:.2f}][概率:{reply_probability:.1f}]\033[0m")
response = ""
# 创建思考消息
if random() < reply_probability:
tinking_time_point = round(time.time(), 2)
think_id = 'mt' + str(tinking_time_point)
thinking_message = Message_Thinking(message=message,message_id=think_id)
message_sender.send_temp_container.add_message(thinking_message)
willing_manager.change_reply_willing_sent(thinking_message.group_id)
# 生成回复
response, emotion = await self.gpt.generate_response(message)
# 如果生成了回复,发送并记录
if response:
message_set = MessageSet(event.group_id, self.config.BOT_QQ, think_id)
if isinstance(response, list):
# 将多条消息合并成一条
accu_typing_time = 0
for msg in response:
# print(f"\033[1;34m[调试]\033[0m 载入消息消息: {msg}")
# bot_response_time = round(time.time(), 2)
timepoint = tinking_time_point-0.3
print(f"当前消息: {msg}")
typing_time = calculate_typing_time(msg)
accu_typing_time += typing_time
timepoint = tinking_time_point+accu_typing_time
# print(f"\033[1;32m[调试]\033[0m 消息: {msg},添加!, 累计打字时间: {accu_typing_time:.2f}秒")
bot_message = Message(
group_id=event.group_id,
user_id=self.config.BOT_QQ,
@@ -167,26 +160,10 @@ class ChatBot:
group_name=message.group_name,
time=timepoint
)
# print(f"\033[1;34m[调试]\033[0m 添加消息到消息组: {bot_message}")
message_set.add_message(bot_message)
# print(f"\033[1;34m[调试]\033[0m 输入消息组: {message_set}")
message_sender.send_temp_container.update_thinking_message(message_set)
else:
# bot_response_time = round(time.time(), 2)
bot_message = Message(
group_id=event.group_id,
user_id=self.config.BOT_QQ,
message_id=think_id,
message_based_id=event.message_id,
raw_message=response,
plain_text=response,
processed_plain_text=response,
user_nickname="麦麦",
group_name=message.group_name,
time=tinking_time_point
)
# print(f"\033[1;34m[调试]\033[0m 更新单条消息: {bot_message}")
message_sender.send_temp_container.update_thinking_message(bot_message)
bot_response_time = tinking_time_point
@@ -197,8 +174,8 @@ class ChatBot:
if random() < 0.5:
bot_response_time = tinking_time_point - 1
# else:
# bot_response_time = bot_response_time + 1
else:
bot_response_time = bot_response_time + 1
bot_message = Message(
group_id=event.group_id,

View File

@@ -5,6 +5,7 @@ name = "MegBot"
[bot]
qq = #填入你的机器人QQ
nickname = "麦麦"
[message]
min_text_length = 2
@@ -16,7 +17,9 @@ check_interval = 120
register_interval = 10
[response]
model_r1_probability = 0.2 #使用R1回复的概率没启用
model_r1_probability = 0.8
model_v3_probability = 0.1
model_r1_distill_probability = 0.1
[groups]

View File

@@ -26,6 +26,7 @@ class BotConfig:
DATABASE_NAME: str = "MegBot"
BOT_QQ: Optional[int] = None
BOT_NICKNAME: Optional[str] = None
# 消息处理相关配置
MIN_TEXT_LENGTH: int = 2 # 最小处理文本长度
@@ -39,7 +40,9 @@ class BotConfig:
EMOJI_CHECK_INTERVAL: int = 120 # 表情包检查间隔(分钟)
EMOJI_REGISTER_INTERVAL: int = 10 # 表情包注册间隔(分钟)
MODEL_R1_PROBABILITY: float = 0.3 # R1模型概率
MODEL_R1_PROBABILITY: float = 0.8 # R1模型概率
MODEL_V3_PROBABILITY: float = 0.1 # V3模型概率
MODEL_R1_DISTILL_PROBABILITY: float = 0.1 # R1蒸馏模型概率
@classmethod
def load_config(cls, config_path: str = "bot_config.toml") -> "BotConfig":
@@ -66,11 +69,13 @@ class BotConfig:
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)
# 消息配置
if "message" in toml_dict:

View File

@@ -1,107 +0,0 @@
from typing import Dict, Optional
from ...common.database import Database
import time
class GroupInfoManager:
def __init__(self):
self.db = Database.get_instance()
# 确保必要的集合存在
self._ensure_collections()
def _ensure_collections(self):
"""确保数据库中有必要的集合"""
collections = self.db.db.list_collection_names()
if 'group_info' not in collections:
self.db.db.create_collection('group_info')
if 'user_info' not in collections:
self.db.db.create_collection('user_info')
async def update_group_info(self, group_id: int, group_name: str, group_notice: str = "",
member_count: int = 0, admins: list = None):
"""更新群组信息"""
try:
group_data = {
"group_id": group_id,
"group_name": group_name,
"group_notice": group_notice,
"member_count": member_count,
"admins": admins or [],
"last_updated": time.time()
}
# 使用 upsert 来更新或插入数据
self.db.db.group_info.update_one(
{"group_id": group_id},
{"$set": group_data},
upsert=True
)
except Exception as e:
print(f"\033[1;31m[错误]\033[0m 更新群信息失败: {str(e)}")
async def update_user_info(self, user_id: int, nickname: str, group_id: int = None,
group_card: str = None, age: int = None, gender: str = None,
location: str = None):
"""更新用户信息"""
try:
# 基础用户数据
user_data = {
"user_id": user_id,
"nickname": nickname,
"last_updated": time.time()
}
# 添加可选字段
if age is not None:
user_data["age"] = age
if gender is not None:
user_data["gender"] = gender
if location is not None:
user_data["location"] = location
# 如果提供了群相关信息,更新用户在该群的信息
if group_id is not None:
group_info_key = f"group_info.{group_id}"
group_data = {
group_info_key: {
"group_card": group_card,
"last_active": time.time()
}
}
user_data.update(group_data)
# 使用 upsert 来更新或插入数据
result = self.db.db.user_info.update_one(
{"user_id": user_id},
{
"$set": user_data,
"$addToSet": {"groups": group_id} if group_id else {}
},
upsert=True
)
# print(f"\033[1;32m[用户信息]\033[0m 更新用户 {nickname}({user_id}) 的信息 {'成功' if result.modified_count > 0 or result.upserted_id else '未变化'}")
except Exception as e:
print(f"\033[1;31m[错误]\033[0m 更新用户信息失败: {str(e)}")
print(f"用户ID: {user_id}, 昵称: {nickname}, 群ID: {group_id}, 群名片: {group_card}")
async def get_group_info(self, group_id: int) -> Optional[Dict]:
"""获取群组信息"""
try:
return self.db.db.group_info.find_one({"group_id": group_id})
except Exception as e:
print(f"\033[1;31m[错误]\033[0m 获取群信息失败: {str(e)}")
return None
async def get_user_info(self, user_id: int, group_id: int = None) -> Optional[Dict]:
"""获取用户信息"""
try:
user_info = self.db.db.user_info.find_one({"user_id": user_id})
if user_info and group_id:
# 添加该用户在特定群的信息
group_info_key = f"group_info.{group_id}"
user_info["current_group_info"] = user_info.get(group_info_key, {})
return user_info
except Exception as e:
print(f"\033[1;31m[错误]\033[0m 获取用户信息失败: {str(e)}")
return None

View File

@@ -14,8 +14,8 @@ from dotenv import load_dotenv
from .relationship_manager import relationship_manager
from ..schedule.schedule_generator import bot_schedule
from .prompt_builder import prompt_builder
from .config import llm_config
from .utils import get_embedding, split_into_sentences, process_text_with_typos
from .config import llm_config, global_config
from .utils import process_llm_response
# 获取当前文件的绝对路径
@@ -38,14 +38,19 @@ class LLMResponseGenerator:
async def generate_response(self, message: Message) -> Optional[Union[str, List[str]]]:
"""根据当前模型类型选择对应的生成函数"""
# 使用随机数选择模型
# 从global_config中获取模型概率值
model_r1_probability = global_config.MODEL_R1_PROBABILITY
model_v3_probability = global_config.MODEL_V3_PROBABILITY
model_r1_distill_probability = global_config.MODEL_R1_DISTILL_PROBABILITY
# 生成随机数并根据概率选择模型
rand = random.random()
if rand < 0.8: # 60%概率使用 R1
self.current_model_type = "r1"
elif rand < 0.5: # 20%概率使用 V3
self.current_model_type = "v3"
else: # 20%概率使用 R1-Distill
self.current_model_type = "r1_distill"
if rand < model_r1_probability:
self.current_model_type = 'r1'
elif rand < model_r1_probability + model_v3_probability:
self.current_model_type = 'v3'
else:
self.current_model_type = 'r1_distill' # 默认使用 R1-Distill
print(f"+++++++++++++++++麦麦{self.current_model_type}思考中+++++++++++++++++")
if self.current_model_type == 'r1':
@@ -64,11 +69,15 @@ class LLMResponseGenerator:
return model_response, emotion
async def _generate_r1_response(self, message: Message) -> Optional[str]:
"""使用 DeepSeek-R1 模型生成回复"""
# 获取群聊上下文
group_chat = await self._get_group_chat_context(message)
async def _generate_base_response(
self,
message: Message,
model_name: str,
model_params: Optional[Dict[str, Any]] = None
) -> Optional[str]:
sender_name = message.user_nickname or f"用户{message.user_id}"
# 获取关系值
if relationship_manager.get_relationship(message.user_id):
relationship_value = relationship_manager.get_relationship(message.user_id).relationship_value
print(f"\033[1;32m[关系管理]\033[0m 回复中_当前关系值: {relationship_value}")
@@ -83,18 +92,30 @@ class LLMResponseGenerator:
group_id=message.group_id
)
# 设置默认参数
default_params = {
"model": model_name,
"messages": [{"role": "user", "content": prompt}],
"stream": False,
"max_tokens": 1024,
"temperature": 0.7
}
# 更新参数
if model_params:
default_params.update(model_params)
def create_completion():
return self.client.chat.completions.create(
model="Pro/deepseek-ai/DeepSeek-R1",
messages=[{"role": "user", "content": prompt}],
stream=False,
max_tokens=1024
)
return self.client.chat.completions.create(**default_params)
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(None, create_completion)
if response.choices[0].message.content:
if not response.choices[0].message.content:
return None
content = response.choices[0].message.content
# 获取推理内容
reasoning_content = "模型思考过程:\n" + prompt
if hasattr(response.choices[0].message, "reasoning"):
@@ -102,124 +123,45 @@ class LLMResponseGenerator:
elif hasattr(response.choices[0].message, "reasoning_content"):
reasoning_content = response.choices[0].message.reasoning_content or reasoning_content
# 保存推理结果到数据库
# 保存到数据库
self.db.db.reasoning_logs.insert_one({
'time': time.time(),
'group_id': message.group_id,
'user': sender_name,
'message': message.processed_plain_text,
'model': "DeepSeek-R1",
'model': model_name,
'reasoning': reasoning_content,
'response': content,
'prompt': prompt
'prompt': prompt,
'model_params': default_params
})
else:
return None
return content
async def _generate_r1_response(self, message: Message) -> Optional[str]:
"""使用 DeepSeek-R1 模型生成回复"""
return await self._generate_base_response(
message,
"Pro/deepseek-ai/DeepSeek-R1",
{"temperature": 0.7, "max_tokens": 1024}
)
async def _generate_v3_response(self, message: Message) -> Optional[str]:
"""使用 DeepSeek-V3 模型生成回复"""
# 获取群聊上下文
group_chat = await self._get_group_chat_context(message)
sender_name = message.user_nickname or f"用户{message.user_id}"
if relationship_manager.get_relationship(message.user_id):
relationship_value = relationship_manager.get_relationship(message.user_id).relationship_value
print(f"\033[1;32m[关系管理]\033[0m 回复中_当前关系值: {relationship_value}")
else:
relationship_value = 0.0
prompt = prompt_builder._build_prompt(message.processed_plain_text, sender_name, relationship_value, group_id=message.group_id)
messages = [{"role": "user", "content": prompt}]
loop = asyncio.get_event_loop()
create_completion = partial(
self.client.chat.completions.create,
model="Pro/deepseek-ai/DeepSeek-V3",
messages=messages,
stream=False,
max_tokens=1024,
temperature=0.8
return await self._generate_base_response(
message,
"Pro/deepseek-ai/DeepSeek-V3",
{"temperature": 0.8, "max_tokens": 1024}
)
response = await loop.run_in_executor(None, create_completion)
if response.choices[0].message.content:
content = response.choices[0].message.content
# 保存推理结果到数据库
self.db.db.reasoning_logs.insert_one({
'time': time.time(),
'group_id': message.group_id,
'user': sender_name,
'message': message.processed_plain_text,
'model': "DeepSeek-V3",
'reasoning': "V3模型无推理过程",
'response': content,
'prompt': prompt
})
return content
else:
print(f"[ERROR] V3 回复发送生成失败: {response}")
return None
async def _generate_r1_distill_response(self, message: Message) -> Optional[str]:
"""使用 DeepSeek-R1-Distill-Qwen-32B 模型生成回复"""
# 获取群聊上下文
group_chat = await self._get_group_chat_context(message)
sender_name = message.user_nickname or f"用户{message.user_id}"
if relationship_manager.get_relationship(message.user_id):
relationship_value = relationship_manager.get_relationship(message.user_id).relationship_value
print(f"\033[1;32m[关系管理]\033[0m 回复中_当前关系值: {relationship_value}")
else:
relationship_value = 0.0
# 构建 prompt
prompt = prompt_builder._build_prompt(
message_txt=message.processed_plain_text,
sender_name=sender_name,
relationship_value=relationship_value,
group_id=message.group_id
return await self._generate_base_response(
message,
"deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
{"temperature": 0.7, "max_tokens": 1024}
)
def create_completion():
return self.client.chat.completions.create(
model="deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
messages=[{"role": "user", "content": prompt}],
stream=False,
max_tokens=1024
)
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(None, create_completion)
if response.choices[0].message.content:
content = response.choices[0].message.content
# 获取推理内容
reasoning_content = "模型思考过程:\n" + prompt
if hasattr(response.choices[0].message, "reasoning"):
reasoning_content = response.choices[0].message.reasoning or reasoning_content
elif hasattr(response.choices[0].message, "reasoning_content"):
reasoning_content = response.choices[0].message.reasoning_content or reasoning_content
# 保存推理结果到数据库
self.db.db.reasoning_logs.insert_one({
'time': time.time(),
'group_id': message.group_id,
'user': sender_name,
'message': message.processed_plain_text,
'model': "DeepSeek-R1-Distill",
'reasoning': reasoning_content,
'response': content,
'prompt': prompt
})
else:
return None
return content
async def _get_group_chat_context(self, message: Message) -> str:
"""获取群聊上下文"""
recent_messages = self.db.db.messages.find(
@@ -271,46 +213,14 @@ class LLMResponseGenerator:
print(f"获取情感标签时出错: {e}")
return ["neutral"] # 发生错误时返回默认值
async def _process_response(self, content: str) -> Tuple[Union[str, List[str]], List[str]]:
async def _process_response(self, content: str) -> Tuple[List[str], List[str]]:
"""处理响应内容,返回处理后的内容和情感标签"""
if not content:
return None, []
# 检查回复是否过长超过200个字符
if len(content) > 200:
print(f"回复过长 ({len(content)} 字符),返回默认回复")
return "麦麦不知道哦", ["angry"]
emotion_tags = await self._get_emotion_tags(content)
# 添加错别字和处理标点符号
processed_response = process_text_with_typos(content)
# 处理长消息
if len(processed_response) > 5:
sentences = split_into_sentences(processed_response)
print(f"分割后的句子: {sentences}")
messages = []
current_message = ""
for sentence in sentences:
if len(current_message) + len(sentence) <= 5:
current_message += ' '
current_message += sentence
else:
if current_message:
messages.append(current_message.strip())
current_message = sentence
if current_message:
messages.append(current_message.strip())
# 检查分割后的消息数量是否过多超过3条
if len(messages) > 3:
print(f"分割后消息数量过多 ({len(messages)} 条),返回默认回复")
return "麦麦不知道哦", ["angry"]
return messages, emotion_tags
processed_response = process_llm_response(content)
return processed_response, emotion_tags

View File

@@ -9,6 +9,7 @@ from PIL import Image
from .config import BotConfig, global_config
import urllib3
from .cq_code import CQCode
from .utils_user import get_user_nickname
Message = ForwardRef('Message') # 添加这行
@@ -44,32 +45,19 @@ class Message:
time: float = None
is_emoji: bool = False # 是否是表情包
has_emoji: bool = False # 是否包含表情包
reply_benefits: float = 0.0
type: str = 'received' # 消息类型可以是received或者send
"""消息数据类:思考消息"""
# 思考状态相关属性
is_thinking: bool = False
thinking_text: str = "正在思考..."
thingking_start_time: float = None
thinking_time: float = 0
received_message = ''
thinking_response = ''
def __post_init__(self):
if self.time is None:
self.time = int(time.time())
if not self.user_nickname:
self.user_nickname = self.get_user_nickname(self.user_id)
self.user_nickname = get_user_nickname(self.user_id)
if not self.group_name:
self.group_name = self.get_groupname(self.group_id)
@@ -84,28 +72,6 @@ class Message:
for seg in self.message_segments
)
# print(f"\033[1;34m[调试]\033[0m pppttt消息: {self.processed_plain_text}")
def get_user_nickname(self, user_id: int) -> str:
"""
根据user_id获取用户昵称
如果数据库中找不到,则返回默认昵称
"""
if not user_id:
return "未知用户"
user_id = int(user_id)
if user_id == int(global_config.BOT_QQ):
return "麦麦"
# 使用数据库单例
db = Database.get_instance()
# 查找用户,打印查询条件和结果
query = {'user_id': user_id}
user = db.db.user_info.find_one(query)
if user:
return user.get('nickname') or f"用户{user_id}"
else:
return f"用户{user_id}"
def get_groupname(self, group_id: int) -> str:
if not group_id:
@@ -127,7 +93,7 @@ class Message:
返回的列表中每个元素都是字典,包含:
- type: 'text' 或 CQ码类型
- data: 对于text类型是文本内容对于CQ码是参数字典
- translated_text: 经过处理如AI翻译后的文本
- translated_text: 经过处理后的文本
"""
segments = []
start = 0
@@ -200,11 +166,15 @@ class Message:
start = cq_end + 1
# 检查是否只包含一个表情包CQ码
if len(segments) == 1 and segments[0]['type'] == 'image':
# 检查图片的 subtype 是否为 0表情包
if segments[0]['data'].get('subtype') == '0':
self.is_emoji = True
self.has_emoji_emoji = True
else:
for segment in segments:
if segment['type'] == 'image' and segment['data'].get('sub_type') == '1':
self.has_emoji_emoji = True
break
return segments
@@ -224,11 +194,6 @@ class Message_Thinking:
self.time = int(time.time())
self.thinking_time = 0
def update_to_message(self, done_message: Message) -> Message:
"""更新为完整消息"""
return done_message
def update_thinking_time(self):
self.thinking_time = round(time.time(), 2) - self.time
@@ -278,21 +243,6 @@ class MessageSet:
return self.messages[left]
def get_latest_message(self) -> Optional[Message]:
"""获取最新的消息"""
return self.messages[-1] if self.messages else None
def get_earliest_message(self) -> Optional[Message]:
"""获取最早的消息"""
return self.messages[0] if self.messages else None
def get_all_messages(self) -> List[Message]:
"""获取所有消息"""
return self.messages.copy()
def get_message_count(self) -> int:
"""获取消息数量"""
return len(self.messages)
def clear_messages(self) -> None:
"""清空所有消息"""
@@ -311,10 +261,7 @@ class MessageSet:
def __len__(self) -> int:
return len(self.messages)
@property
def processed_plain_text(self) -> str:
"""获取所有消息的文本内容"""
return "\n".join(msg.processed_plain_text for msg in self.messages if msg.processed_plain_text)

View File

@@ -6,7 +6,7 @@ from .message import Message, Message_Thinking, MessageSet
from .cq_code import CQCode
from collections import deque
import time
from .storage import MessageStorage # 添加这行导入
from .storage import MessageStorage
from .config import global_config
from .message_visualizer import message_visualizer
@@ -50,11 +50,6 @@ class SendTemp:
def get_earliest_message(self) -> Optional[Message]:
"""获取时间最早的消息"""
message = self.messages.popleft() if self.messages else None
# 如果是思考中的消息且思考时间不够,重新加入队列
# if (isinstance(message, Message_Thinking) and
# time.time() - message.start_time < 2): # 最少思考2秒
# self.messages.appendleft(message)
# return None
return message
def clear(self) -> None:
@@ -120,16 +115,7 @@ class SendTempContainer:
return list(self.temp_queues.keys())
def update_thinking_message(self, message_obj: Union[Message, MessageSet]) -> bool:
"""更新思考中的消息
Args:
message_obj: 要更新的消息对象,可以是单条消息或消息组
Returns:
bool: 更新是否成功
"""
queue = self.get_queue(message_obj.group_id)
# 使用列表解析找到匹配的消息索引
matching_indices = [
i for i, msg in enumerate(queue.messages)
@@ -181,43 +167,29 @@ class MessageSendControl:
"""设置当前bot实例"""
self._current_bot = bot
async def start_processor(self, bot: Bot):
"""启动消息处理器"""
self._current_bot = bot
while self._running:
await asyncio.sleep(1.5)
# 处理所有群组的消息队列
for group_id in self.send_temp_container.get_all_groups():
async def process_group_messages(self, group_id: int):
queue = self.send_temp_container.get_queue(group_id)
if queue.has_messages():
message = queue.peek_next()
# print(f"\033[1;34m[调试]\033[0m 查看最早的消息: {message}")
if message:
# 处理消息的逻辑
if isinstance(message, Message_Thinking):
# 如果是思考中的消息,检查是否需要继续等待
message.update_thinking_time()
thinking_time = message.thinking_time
if thinking_time < 60: # 最少思考2秒
if int(thinking_time) % 10 == 0:
print(f"\033[1;34m[调试]\033[0m 消息正在思考中,已思考{thinking_time:.1f}")
continue
return
else:
print(f"\033[1;34m[调试]\033[0m 思考消息超时,移除")
queue.get_earliest_message() # 移除超时的思考消息
return
elif isinstance(message, Message):
message = queue.get_earliest_message()
if message and message.processed_plain_text:
print(f"- 群组: {group_id} - 内容: {message.processed_plain_text}")
cost_time = round(time.time(), 2) - message.time
# print(f"\033[1;34m[调试]\033[0m 消息发送111111时间: {cost_time}秒")
if cost_time > 40:
message.processed_plain_text = CQCode.create_reply_cq(message.message_based_id) + message.processed_plain_text
cur_time = time.time()
await self._current_bot.send_group_msg(
group_id=group_id,
@@ -226,14 +198,10 @@ class MessageSendControl:
)
cost_time = round(time.time(), 2) - cur_time
print(f"\033[1;34m[调试]\033[0m 消息发送时间: {cost_time}")
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.time))
print(f"\033[1;32m群 {group_id} 消息, 用户 麦麦, 时间: {current_time}:\033[0m {str(message.processed_plain_text)}")
await self.storage.store_message(message, None)
queue.update_send_time()
if queue.has_messages():
await asyncio.sleep(
random.uniform(
@@ -241,89 +209,24 @@ class MessageSendControl:
self.message_interval[1]
)
)
message_visualizer.update_content(self.send_temp_container)
async def start_processor(self, bot: Bot):
"""启动消息处理器"""
self._current_bot = bot
async def process_group_queue(self, bot: Bot, group_id: int) -> None:
"""处理指定群组的消息队列"""
queue = self.send_temp_container.get_queue(group_id)
while queue.has_messages():
message = queue.get_earliest_message()
if message and message.processed_plain_text:
await self.send_message(
bot=bot,
group_id=group_id,
content=message.processed_plain_text
)
queue.update_send_time()
if queue.has_messages():
await asyncio.sleep(
random.uniform(self.message_interval[0], self.message_interval[1])
)
async def process_all_queues(self, bot: Bot) -> None:
"""处理所有群组的消息队列"""
if not self._running or self._paused:
return
while self._running:
await asyncio.sleep(1.5)
tasks = []
for group_id in self.send_temp_container.get_all_groups():
await self.process_group_queue(bot, group_id)
tasks.append(self.process_group_messages(group_id))
async def send_temp_message(self,
bot: Bot,
group_id: int,
message: Union[Message, Message_Thinking],
with_emoji: bool = False,
emoji_path: Optional[str] = None) -> bool:
"""
发送单个临时消息
Args:
bot: Bot实例
group_id: 群组ID
message: Message对象
with_emoji: 是否带表情
emoji_path: 表情图片路径
Returns:
bool: 发送是否成功
"""
try:
if with_emoji and emoji_path:
return await self.send_with_emoji(
bot=bot,
group_id=group_id,
text_content=message.processed_plain_text,
emoji_path=emoji_path
)
else:
return await self.send_message(
bot=bot,
group_id=group_id,
content=message.processed_plain_text
)
except Exception as e:
print(f"\033[1;31m[错误]\033[0m 发送临时消息失败: {str(e)}")
return False
# 并行处理所有群组的消息
await asyncio.gather(*tasks)
message_visualizer.update_content(self.send_temp_container)
def set_typing_speed(self, min_speed: float, max_speed: float):
"""设置打字速度范围"""
self.typing_speed = (min_speed, max_speed)
def set_message_interval(self, min_interval: float, max_interval: float):
"""设置消息间隔范围"""
self.message_interval = (min_interval, max_interval)
def pause(self):
"""暂停消息处理"""
self._paused = True
def resume(self):
"""恢复消息处理"""
self._paused = False
def stop(self):
"""停止消息处理"""
self._running = False
# 创建全局实例
message_sender = MessageSendControl()

View File

@@ -92,7 +92,7 @@ class MessageVisualizer:
display_text += f"群组: {msg.group_id}\n"
display_text += f"用户: {msg.user_nickname}({msg.user_id})\n"
display_text += f"内容: {msg.thinking_text}\n"
display_text += f"思考时间: {msg.thinking_time}\n"
display_text += f"思考时间: {int(msg.thinking_time)}\n"
else:
display_text += f"类型: 普通消息\n"
display_text += f"时间: {msg_time}\n"

View File

@@ -90,17 +90,29 @@ class PromptBuilder:
if personality_choice < 4/6: # 第一种人格
prompt_personality = f'''你的网名叫麦麦是一个学习地质的女大学生喜欢摄影你会刷贴吧你正在浏览qq群,{promt_info_prompt},
{activate_prompt}
现在请你给出日常且口语化的回复,平淡一些,尽量简短一些,不要输出感叹号{is_bot_prompt}
现在请你给出日常且口语化的回复,平淡一些,尽量简短一些。{is_bot_prompt}
请注意把握群里的聊天内容,不要回复的太有条理,可以有个性。'''
elif personality_choice < 1: # 第二种人格
prompt_personality = f'''你的网名叫麦麦,是一个女大学生,你有黑色头发,你会刷小红书, 你正在浏览qq群,{promt_info_prompt},
{activate_prompt}
现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些,不要输出感叹号{is_bot_prompt}
现在请你给出日常且口语化的回复,请表现你自己的见解,不要一昧迎合,尽量简短一些。{is_bot_prompt}
请你表达自己的见解和观点。可以有个性。'''
#中文高手
prompt_ger = ''
if random.random() < 0.04:
prompt_ger += '你喜欢用倒装句'
if random.random() < 0.02:
prompt_ger += '你喜欢用反问句'
if random.random() < 0.01:
prompt_ger += '你喜欢用文言文'
#额外信息要求
extra_info = '''但是记得回复平淡一些,简短一些,不要过多提及自身的背景, 记住不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只需要输出回复内容就好,不要输出其他任何内容'''
#合并prompt
prompt = ""
# prompt += f"{prompt_info}\n"
@@ -108,6 +120,7 @@ class PromptBuilder:
prompt += f"{chat_talking_prompt}\n"
# prompt += f"{activate_prompt}\n"
prompt += f"{prompt_personality}\n"
prompt += f"{prompt_ger}\n"
prompt += f"{extra_info}\n"
return prompt

View File

@@ -46,8 +46,8 @@ class Relationship:
class RelationshipManager:
def __init__(self):
self.relationships: dict[int, Relationship] = {} # user_id -> Relationship
#保存 qq号现在使用昵称别称
self.id_name_nickname_table: dict[str, str, list] = {} # name -> [nickname, nickname, ...]
# self.id_name_nickname_table: dict[str, list] = {} # name -> [nickname, nickname, ...]
# print("[关系管理] 初始化 id_name_nickname_table") # 调试信息
async def update_relationship(self, user_id: int, data=None, **kwargs):
# 检查是否在内存中已存在
@@ -67,6 +67,9 @@ class RelationshipManager:
relationship = Relationship(user_id, data=data) if isinstance(data, dict) else Relationship(user_id, **kwargs)
self.relationships[user_id] = relationship
# 更新 id_name_nickname_table
# self.id_name_nickname_table[user_id] = [relationship.nickname] # 别称设置为空列表
# 保存到数据库
await self.storage_relationship(relationship)
relationship.saved = True
@@ -150,30 +153,11 @@ class RelationshipManager:
upsert=True
)
@staticmethod
async def get_user_nickname(bot: Bot, user_id: int, group_id: int = None) -> Tuple[str, Optional[str]]:
"""
通过QQ API获取用户昵称
"""
# 获取QQ昵称
stranger_info = await bot.get_stranger_info(user_id=user_id)
qq_nickname = stranger_info['nickname']
# 如果提供了群号,获取群昵称
if group_id:
try:
member_info = await bot.get_group_member_info(
group_id=group_id,
user_id=user_id,
no_cache=True
)
group_nickname = member_info['card'] or None
return qq_nickname, group_nickname
except:
return qq_nickname, None
return qq_nickname, None
def get_name(self, user_id: int) -> str:
if user_id in self.relationships:
return self.relationships[user_id].nickname
else:
return "[某人]"
def print_all_relationships(self):
"""打印内存中所有的关系记录"""
@@ -193,8 +177,4 @@ class RelationshipManager:
print("=" * 50)
relationship_manager = RelationshipManager()

View File

@@ -5,6 +5,8 @@ from .message import Message
import requests
import numpy as np
from .config import llm_config
import re
def combine_messages(messages: List[Message]) -> str:
"""将消息列表组合成格式化的字符串
@@ -115,52 +117,85 @@ def get_recent_group_messages(db, group_id: int, limit: int = 12) -> list:
message_objects.reverse()
return message_objects
def split_into_sentences(text: str) -> List[str]:
def split_into_sentences_w_remove_punctuation(text: str) -> List[str]:
"""将文本分割成句子,但保持书名号中的内容完整
Args:
text: 要分割的文本字符串
Returns:
List[str]: 分割后的句子列表
"""
delimiters = ['', '', '', ',', '', '', '!', '?', '\n'] # 添加换行符作为分隔符
remove_chars = ['', ','] # 只移除这两种逗号
sentences = []
current_sentence = ""
in_book_title = False # 标记是否在书名号内
len_text = len(text)
if len_text < 5:
if random.random() < 0.01:
return list(text) # 如果文本很短且触发随机条件,直接按字符分割
else:
return [text]
if len_text < 12:
split_strength = 0.3
elif len_text < 32:
split_strength = 0.7
else:
split_strength = 0.9
#先移除换行符
# print(f"split_strength: {split_strength}")
for char in text:
current_sentence += char
# print(f"处理前的文本: {text}")
# 检查书名
if char == '':
in_book_title = True
elif char == '':
in_book_title = False
# 统一将英文逗号转换为中文逗
text = text.replace(',', '')
text = text.replace('\n', ' ')
# 只有不在书名号内且是分隔符时才分割
if char in delimiters and not in_book_title:
if current_sentence.strip(): # 确保不是空字符串
# 只移除逗号
clean_sentence = current_sentence
if clean_sentence[-1] in remove_chars:
clean_sentence = clean_sentence[:-1]
if clean_sentence.strip():
sentences.append(clean_sentence.strip())
current_sentence = ""
# print(f"处理前的文本: {text}")
# 处理最后一个句子
if current_sentence.strip():
# 如果最后一个字符是逗号,移除它
if current_sentence[-1] in remove_chars:
current_sentence = current_sentence[:-1]
sentences.append(current_sentence.strip())
text_no_1 = ''
for letter in text:
# print(f"当前字符: {letter}")
if letter in ['!','','?','']:
# print(f"当前字符: {letter}, 随机数: {random.random()}")
if random.random() < split_strength:
letter = ''
if letter in ['','']:
# print(f"当前字符: {letter}, 随机数: {random.random()}")
if random.random() < 1 - split_strength:
letter = ''
text_no_1 += letter
# 过滤掉空字符串
sentences = [s for s in sentences if s.strip()]
# 对每个逗号单独判断是否分割
sentences = [text_no_1]
new_sentences = []
for sentence in sentences:
parts = sentence.split('')
current_sentence = parts[0]
for part in parts[1:]:
if random.random() < split_strength:
new_sentences.append(current_sentence.strip())
current_sentence = part
else:
current_sentence += '' + part
# 处理空格分割
space_parts = current_sentence.split(' ')
current_sentence = space_parts[0]
for part in space_parts[1:]:
if random.random() < split_strength:
new_sentences.append(current_sentence.strip())
current_sentence = part
else:
current_sentence += ' ' + part
new_sentences.append(current_sentence.strip())
sentences = [s for s in new_sentences if s] # 移除空字符串
return sentences
# print(f"分割后的句子: {sentences}")
sentences_done = []
for sentence in sentences:
sentence = sentence.rstrip(',')
if random.random() < split_strength*0.5:
sentence = sentence.replace('', '').replace(',', '')
elif random.random() < split_strength:
sentence = sentence.replace('', ' ').replace(',', ' ')
sentences_done.append(sentence)
print(f"处理后的句子: {sentences_done}")
return sentences_done
# 常见的错别字映射
TYPO_DICT = {
@@ -259,16 +294,7 @@ def random_remove_punctuation(text: str) -> str:
return result
def add_typos(text: str) -> str:
"""随机给文本添加错别字
Args:
text: 要处理的文本
Returns:
str: 添加错别字后的文本
"""
TYPO_RATE = 0.02 # 控制错别字出现的概率(2%)
result = ""
for char in text:
if char in TYPO_DICT and random.random() < TYPO_RATE:
@@ -279,15 +305,33 @@ def add_typos(text: str) -> str:
result += char
return result
def process_text_with_typos(text: str) -> str:
"""处理文本,添加错别字和处理标点符号
def process_llm_response(text: str) -> List[str]:
# processed_response = process_text_with_typos(content)
if len(text) > 200:
print(f"回复过长 ({len(text)} 字符),返回默认回复")
return ['懒得说']
# 处理长消息
sentences = split_into_sentences_w_remove_punctuation(add_typos(text))
# 检查分割后的消息数量是否过多超过3条
if len(sentences) > 3:
print(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复")
return ['麦麦不知道哦']
Args:
text: 要处理的文本
return sentences
Returns:
str: 处理后的文本
def calculate_typing_time(input_string: str, chinese_time: float = 0.2, english_time: float = 0.1) -> float:
"""
if random.random() < 0.9: # 90%概率进行处理
return random_remove_punctuation(add_typos(text))
return text
计算输入字符串所需的时间,中文和英文字符有不同的输入时间
input_string (str): 输入的字符串
chinese_time (float): 中文字符的输入时间默认为0.3秒
english_time (float): 英文字符的输入时间默认为0.15秒
"""
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

View File

@@ -0,0 +1,7 @@
from .relationship_manager import relationship_manager
from .config import global_config
def get_user_nickname(user_id: int) -> str:
if user_id == int(global_config.BOT_QQ):
return global_config.BOT_NICKNAME
return relationship_manager.get_name(user_id)

View File

@@ -27,12 +27,12 @@ class WillingManager:
current_willing = self.group_reply_willing.get(group_id, 0)
if topic and current_willing < 1:
current_willing += 0.4
current_willing += 0.3
elif topic:
current_willing += 0.05
if is_mentioned_bot and current_willing < 1.0:
current_willing += 1
current_willing += 0.9
elif is_mentioned_bot:
current_willing += 0.05
@@ -47,7 +47,7 @@ class WillingManager:
reply_probability = 0
if group_id in config.talk_frequency_down_groups:
reply_probability = reply_probability / 2
reply_probability = reply_probability / 3.5
if is_mentioned_bot and user_id == int(1026294844):
reply_probability = 1
@@ -62,9 +62,8 @@ class WillingManager:
def change_reply_willing_after_sent(self, group_id: int):
"""发送消息后提高群组的回复意愿"""
current_willing = self.group_reply_willing.get(group_id, 0)
# 如果当前意愿小于1增加0.3的意愿值
if current_willing < 1:
self.group_reply_willing[group_id] = min(1, current_willing + 0.8)
self.group_reply_willing[group_id] = min(1, current_willing + 0.4)
async def ensure_started(self):
"""确保衰减任务已启动"""