mirror of https://github.com/Mai-with-u/MaiBot.git
🤖 自动格式化代码 [skip ci]
parent
0c2e94c76c
commit
bde5c33fc2
6
bot.py
6
bot.py
|
|
@ -225,15 +225,15 @@ def raw_main():
|
|||
|
||||
# 确保 NicknameManager 单例实例存在并已初始化
|
||||
# (单例模式下,导入时或第一次调用时会自动初始化)
|
||||
_ = nickname_manager # 显式引用一次
|
||||
_ = nickname_manager # 显式引用一次
|
||||
|
||||
# 启动 NicknameManager 的后台处理器线程
|
||||
logger.info("准备启动绰号处理管理器...")
|
||||
nickname_manager.start_processor() # 调用实例的方法
|
||||
nickname_manager.start_processor() # 调用实例的方法
|
||||
logger.info("已调用启动绰号处理管理器。")
|
||||
|
||||
# 注册 NicknameManager 的停止方法到 atexit,确保程序退出时线程能被清理
|
||||
atexit.register(nickname_manager.stop_processor) # 注册实例的方法
|
||||
atexit.register(nickname_manager.stop_processor) # 注册实例的方法
|
||||
logger.info("已注册绰号处理管理器的退出处理程序。")
|
||||
|
||||
# 返回MainSystem实例
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ from typing import Optional
|
|||
|
||||
logger = get_logger("nickname_db")
|
||||
|
||||
|
||||
class NicknameDB:
|
||||
"""
|
||||
处理与群组绰号相关的数据库操作 (MongoDB)。
|
||||
封装了对 'person_info' 集合的读写操作。
|
||||
"""
|
||||
|
||||
def __init__(self, person_info_collection: Optional[Collection]):
|
||||
"""
|
||||
初始化 NicknameDB 处理器。
|
||||
|
|
@ -71,10 +73,10 @@ class NicknameDB:
|
|||
logger.error(
|
||||
f"数据库操作失败 (DuplicateKeyError): person_id {person_id}. 错误: {dk_err}. 这不应该发生,请检查 person_id 生成逻辑和数据库状态。"
|
||||
)
|
||||
raise # 将异常向上抛出
|
||||
raise # 将异常向上抛出
|
||||
except Exception as e:
|
||||
logger.exception(f"对 person_id {person_id} 执行 Upsert 时失败: {e}")
|
||||
raise # 将异常向上抛出
|
||||
raise # 将异常向上抛出
|
||||
|
||||
def update_group_nickname_count(self, person_id: str, group_id_str: str, nickname: str):
|
||||
"""
|
||||
|
|
@ -105,20 +107,20 @@ class NicknameDB:
|
|||
)
|
||||
if result_inc.modified_count > 0:
|
||||
# logger.debug(f"成功增加 person_id {person_id} 在群组 {group_id_str} 中绰号 '{nickname}' 的计数。")
|
||||
return # 成功增加计数,操作完成
|
||||
return # 成功增加计数,操作完成
|
||||
|
||||
# 3b. 如果上一步未修改 (绰号不存在于该群组),尝试将新绰号添加到现有群组
|
||||
result_push_nick = self.person_info_collection.update_one(
|
||||
{
|
||||
"person_id": person_id,
|
||||
"group_nicknames.group_id": group_id_str, # 检查群组是否存在
|
||||
"group_nicknames.group_id": group_id_str, # 检查群组是否存在
|
||||
},
|
||||
{"$push": {"group_nicknames.$[group].nicknames": {"name": nickname, "count": 1}}},
|
||||
array_filters=[{"group.group_id": group_id_str}],
|
||||
)
|
||||
if result_push_nick.modified_count > 0:
|
||||
logger.debug(f"成功为 person_id {person_id} 在现有群组 {group_id_str} 中添加新绰号 '{nickname}'。")
|
||||
return # 成功添加绰号,操作完成
|
||||
return # 成功添加绰号,操作完成
|
||||
|
||||
# 3c. 如果上一步也未修改 (群组条目本身不存在),则添加新的群组条目和绰号
|
||||
# 确保 group_nicknames 数组存在 (作为保险措施)
|
||||
|
|
@ -130,7 +132,7 @@ class NicknameDB:
|
|||
result_push_group = self.person_info_collection.update_one(
|
||||
{
|
||||
"person_id": person_id,
|
||||
"group_nicknames.group_id": {"$ne": group_id_str}, # 确保该群组 ID 尚未存在
|
||||
"group_nicknames.group_id": {"$ne": group_id_str}, # 确保该群组 ID 尚未存在
|
||||
},
|
||||
{
|
||||
"$push": {
|
||||
|
|
@ -144,7 +146,7 @@ class NicknameDB:
|
|||
if result_push_group.modified_count > 0:
|
||||
logger.debug(f"为 person_id {person_id} 添加了新的群组 {group_id_str} 和绰号 '{nickname}'。")
|
||||
# else:
|
||||
# logger.warning(f"尝试为 person_id {person_id} 添加新群组 {group_id_str} 失败,可能群组已存在但结构不符合预期。")
|
||||
# logger.warning(f"尝试为 person_id {person_id} 添加新群组 {group_id_str} 失败,可能群组已存在但结构不符合预期。")
|
||||
|
||||
except (OperationFailure, DuplicateKeyError) as db_err:
|
||||
logger.exception(
|
||||
|
|
@ -152,5 +154,7 @@ class NicknameDB:
|
|||
)
|
||||
# 根据需要决定是否向上抛出 raise db_err
|
||||
except Exception as e:
|
||||
logger.exception(f"更新群组绰号计数时发生意外错误: person_id {person_id}, group {group_id_str}, nick {nickname}. Error: {e}")
|
||||
# 根据需要决定是否向上抛出 raise e
|
||||
logger.exception(
|
||||
f"更新群组绰号计数时发生意外错误: person_id {person_id}, group {group_id_str}, nick {nickname}. Error: {e}"
|
||||
)
|
||||
# 根据需要决定是否向上抛出 raise e
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from .nickname_utils import select_nicknames_for_prompt, format_nickname_prompt_
|
|||
|
||||
# 依赖于 person_info_manager 来生成 person_id
|
||||
from ..person_info.person_info import person_info_manager
|
||||
|
||||
# 依赖于 relationship_manager 来获取用户名称和现有绰号
|
||||
from ..person_info.relationship_manager import relationship_manager
|
||||
|
||||
|
|
@ -28,11 +29,13 @@ from src.plugins.utils.chat_message_builder import build_readable_messages, get_
|
|||
|
||||
logger = get_logger("NicknameManager")
|
||||
|
||||
|
||||
class NicknameManager:
|
||||
"""
|
||||
管理群组绰号分析、处理、存储和使用的单例类。
|
||||
封装了 LLM 调用、后台处理线程和数据库交互。
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
|
|
@ -44,7 +47,7 @@ class NicknameManager:
|
|||
if not cls._instance:
|
||||
logger.info("正在创建 NicknameManager 单例实例...")
|
||||
cls._instance = super(NicknameManager, cls).__new__(cls)
|
||||
cls._instance._initialized = False # 添加初始化标志
|
||||
cls._instance._initialized = False # 添加初始化标志
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -52,7 +55,7 @@ class NicknameManager:
|
|||
初始化 NicknameManager。
|
||||
使用锁和标志确保实际初始化只执行一次。
|
||||
"""
|
||||
if self._initialized: # 如果已初始化,直接返回
|
||||
if self._initialized: # 如果已初始化,直接返回
|
||||
return
|
||||
|
||||
with self._lock:
|
||||
|
|
@ -65,11 +68,11 @@ class NicknameManager:
|
|||
self.is_enabled = self.config.ENABLE_NICKNAME_MAPPING
|
||||
|
||||
# 数据库处理器
|
||||
person_info_collection = getattr(db, 'person_info', None)
|
||||
person_info_collection = getattr(db, "person_info", None)
|
||||
self.db_handler = NicknameDB(person_info_collection)
|
||||
if not self.db_handler.is_available():
|
||||
logger.error("数据库处理器初始化失败,NicknameManager 功能受限。")
|
||||
self.is_enabled = False # 如果数据库不可用,禁用功能
|
||||
self.is_enabled = False # 如果数据库不可用,禁用功能
|
||||
|
||||
# LLM 映射器
|
||||
self.llm_mapper: Optional[LLMRequest] = None
|
||||
|
|
@ -79,8 +82,8 @@ class NicknameManager:
|
|||
if model_config and model_config.get("name"):
|
||||
self.llm_mapper = LLMRequest(
|
||||
model=model_config,
|
||||
temperature=model_config.get("temp", 0.5), # 使用 get 获取并提供默认值
|
||||
max_tokens=model_config.get("max_tokens", 256), # 使用 get 获取并提供默认值
|
||||
temperature=model_config.get("temp", 0.5), # 使用 get 获取并提供默认值
|
||||
max_tokens=model_config.get("max_tokens", 256), # 使用 get 获取并提供默认值
|
||||
request_type="nickname_mapping",
|
||||
)
|
||||
logger.info("绰号映射 LLM 映射器初始化成功。")
|
||||
|
|
@ -103,7 +106,7 @@ class NicknameManager:
|
|||
self._nickname_thread: Optional[threading.Thread] = None
|
||||
self.sleep_interval = getattr(self.config, "NICKNAME_PROCESS_SLEEP_INTERVAL", 0.5)
|
||||
|
||||
self._initialized = True # 标记为已初始化
|
||||
self._initialized = True # 标记为已初始化
|
||||
logger.info("NicknameManager 初始化完成。")
|
||||
|
||||
# 公共方法
|
||||
|
|
@ -115,10 +118,10 @@ class NicknameManager:
|
|||
return
|
||||
if self._nickname_thread is None or not self._nickname_thread.is_alive():
|
||||
logger.info("正在启动绰号处理器线程...")
|
||||
self._stop_event.clear() # 清除停止事件标志
|
||||
self._stop_event.clear() # 清除停止事件标志
|
||||
self._nickname_thread = threading.Thread(
|
||||
target=self._run_processor_in_thread, # 线程执行的入口函数
|
||||
daemon=True # 设置为守护线程,主程序退出时自动结束
|
||||
target=self._run_processor_in_thread, # 线程执行的入口函数
|
||||
daemon=True, # 设置为守护线程,主程序退出时自动结束
|
||||
)
|
||||
self._nickname_thread.start()
|
||||
logger.info(f"绰号处理器线程已启动 (ID: {self._nickname_thread.ident})")
|
||||
|
|
@ -129,7 +132,7 @@ class NicknameManager:
|
|||
"""停止后台处理线程。"""
|
||||
if self._nickname_thread and self._nickname_thread.is_alive():
|
||||
logger.info("正在停止绰号处理器线程...")
|
||||
self._stop_event.set() # 设置停止事件标志
|
||||
self._stop_event.set() # 设置停止事件标志
|
||||
try:
|
||||
# 可选:尝试清空队列,避免丢失未处理的任务
|
||||
# while not self.nickname_queue.empty():
|
||||
|
|
@ -140,7 +143,7 @@ class NicknameManager:
|
|||
# break
|
||||
# logger.info("绰号处理队列已清空。")
|
||||
|
||||
self._nickname_thread.join(timeout=10) # 等待线程结束,设置超时
|
||||
self._nickname_thread.join(timeout=10) # 等待线程结束,设置超时
|
||||
if self._nickname_thread.is_alive():
|
||||
logger.warning("绰号处理器线程在超时后仍未停止。")
|
||||
except Exception as e:
|
||||
|
|
@ -148,7 +151,7 @@ class NicknameManager:
|
|||
finally:
|
||||
if self._nickname_thread and not self._nickname_thread.is_alive():
|
||||
logger.info("绰号处理器线程已成功停止。")
|
||||
self._nickname_thread = None # 清理线程对象引用
|
||||
self._nickname_thread = None # 清理线程对象引用
|
||||
else:
|
||||
logger.info("绰号处理器线程未在运行或已被清理。")
|
||||
|
||||
|
|
@ -163,7 +166,7 @@ class NicknameManager:
|
|||
取代了旧的 trigger_nickname_analysis_if_needed 函数。
|
||||
"""
|
||||
if not self.is_enabled:
|
||||
return # 功能禁用则直接返回
|
||||
return # 功能禁用则直接返回
|
||||
|
||||
current_chat_stream = chat_stream or anchor_message.chat_stream
|
||||
if not current_chat_stream or not current_chat_stream.group_info:
|
||||
|
|
@ -183,8 +186,11 @@ class NicknameManager:
|
|||
# 格式化历史记录
|
||||
chat_history_str = await build_readable_messages(
|
||||
messages=history_messages,
|
||||
replace_bot_name=True, merge_messages=False, timestamp_mode="relative",
|
||||
read_mark=0.0, truncate=False,
|
||||
replace_bot_name=True,
|
||||
merge_messages=False,
|
||||
timestamp_mode="relative",
|
||||
read_mark=0.0,
|
||||
truncate=False,
|
||||
)
|
||||
|
||||
# 2. 获取 Bot 回复
|
||||
|
|
@ -195,7 +201,9 @@ class NicknameManager:
|
|||
platform = current_chat_stream.platform
|
||||
|
||||
# 4. 构建用户 ID 到名称的映射 (user_name_map)
|
||||
user_ids_in_history = {str(msg["user_info"]["user_id"]) for msg in history_messages if msg.get("user_info", {}).get("user_id")}
|
||||
user_ids_in_history = {
|
||||
str(msg["user_info"]["user_id"]) for msg in history_messages if msg.get("user_info", {}).get("user_id")
|
||||
}
|
||||
user_name_map = {}
|
||||
if user_ids_in_history:
|
||||
try:
|
||||
|
|
@ -212,28 +220,29 @@ class NicknameManager:
|
|||
else:
|
||||
# 回退查找历史记录中的 nickname
|
||||
latest_nickname = next(
|
||||
(m["user_info"].get("user_nickname")
|
||||
for m in reversed(history_messages)
|
||||
if str(m["user_info"].get("user_id")) == user_id and m["user_info"].get("user_nickname")),
|
||||
(
|
||||
m["user_info"].get("user_nickname")
|
||||
for m in reversed(history_messages)
|
||||
if str(m["user_info"].get("user_id")) == user_id and m["user_info"].get("user_nickname")
|
||||
),
|
||||
None,
|
||||
)
|
||||
user_name_map[user_id] = latest_nickname or f"未知({user_id})"
|
||||
|
||||
# 5. 添加到内部处理队列
|
||||
item = (chat_history_str, bot_reply_str, platform, group_id, user_name_map)
|
||||
self._add_to_queue(item, platform, group_id) # 调用私有方法入队
|
||||
self._add_to_queue(item, platform, group_id) # 调用私有方法入队
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{log_prefix} 触发绰号分析时出错: {e}", exc_info=True)
|
||||
|
||||
|
||||
async def get_nickname_prompt_injection(self, chat_stream: ChatStream, message_list_before_now: List[Dict]) -> str:
|
||||
"""
|
||||
获取并格式化用于 Prompt 注入的绰号信息字符串。
|
||||
取代了旧的 get_nickname_injection_for_prompt 函数。
|
||||
"""
|
||||
if not self.is_enabled or not chat_stream or not chat_stream.group_info:
|
||||
return "" # 功能禁用或非群聊则返回空
|
||||
return "" # 功能禁用或非群聊则返回空
|
||||
|
||||
log_prefix = f"[{chat_stream.stream_id}]"
|
||||
try:
|
||||
|
|
@ -241,7 +250,11 @@ class NicknameManager:
|
|||
platform = chat_stream.platform
|
||||
|
||||
# 确定上下文中的用户 ID
|
||||
user_ids_in_context = {str(msg["user_info"]["user_id"]) for msg in message_list_before_now if msg.get("user_info", {}).get("user_id")}
|
||||
user_ids_in_context = {
|
||||
str(msg["user_info"]["user_id"])
|
||||
for msg in message_list_before_now
|
||||
if msg.get("user_info", {}).get("user_id")
|
||||
}
|
||||
|
||||
# 如果消息列表为空,尝试获取最近发言者
|
||||
if not user_ids_in_context:
|
||||
|
|
@ -265,12 +278,11 @@ class NicknameManager:
|
|||
logger.debug(f"{log_prefix} 生成的绰号 Prompt 注入:\n{injection_str}")
|
||||
return injection_str
|
||||
else:
|
||||
return "" # 没有获取到绰号数据
|
||||
return "" # 没有获取到绰号数据
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{log_prefix} 获取绰号注入时出错: {e}", exc_info=True)
|
||||
return "" # 出错时返回空
|
||||
|
||||
return "" # 出错时返回空
|
||||
|
||||
# 私有/内部方法
|
||||
|
||||
|
|
@ -278,13 +290,16 @@ class NicknameManager:
|
|||
"""将项目添加到内部处理队列。"""
|
||||
try:
|
||||
self.nickname_queue.put_nowait(item)
|
||||
logger.debug(f"已将项目添加到平台 '{platform}' 群组 '{group_id}' 的绰号队列。当前大小: {self.nickname_queue.qsize()}")
|
||||
logger.debug(
|
||||
f"已将项目添加到平台 '{platform}' 群组 '{group_id}' 的绰号队列。当前大小: {self.nickname_queue.qsize()}"
|
||||
)
|
||||
except queue.Full:
|
||||
logger.warning(f"绰号队列已满 (最大={self.queue_max_size})。平台 '{platform}' 群组 '{group_id}' 的项目被丢弃。")
|
||||
logger.warning(
|
||||
f"绰号队列已满 (最大={self.queue_max_size})。平台 '{platform}' 群组 '{group_id}' 的项目被丢弃。"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"将项目添加到绰号队列时出错: {e}", exc_info=True)
|
||||
|
||||
|
||||
async def _analyze_and_update_nicknames(self, item: tuple):
|
||||
"""处理单个队列项目:调用 LLM 分析并更新数据库。"""
|
||||
if not isinstance(item, tuple) or len(item) != 5:
|
||||
|
|
@ -326,7 +341,9 @@ class NicknameManager:
|
|||
# 步骤 1: 生成 person_id
|
||||
person_id = person_info_manager.get_person_id(platform, user_id_str)
|
||||
if not person_id:
|
||||
logger.error(f"{log_prefix} 无法为 platform='{platform}', user_id='{user_id_str}' 生成 person_id,跳过此用户。")
|
||||
logger.error(
|
||||
f"{log_prefix} 无法为 platform='{platform}', user_id='{user_id_str}' 生成 person_id,跳过此用户。"
|
||||
)
|
||||
continue
|
||||
|
||||
# 步骤 2: 确保 Person 文档存在 (调用 DB Handler)
|
||||
|
|
@ -335,7 +352,7 @@ class NicknameManager:
|
|||
# 步骤 3: 更新群组绰号 (调用 DB Handler)
|
||||
self.db_handler.update_group_nickname_count(person_id, group_id, nickname)
|
||||
|
||||
except (OperationFailure, DuplicateKeyError) as db_err: # 捕获特定的数据库错误
|
||||
except (OperationFailure, DuplicateKeyError) as db_err: # 捕获特定的数据库错误
|
||||
logger.exception(
|
||||
f"{log_prefix} 数据库操作失败 ({type(db_err).__name__}): 用户 {user_id_str}, 绰号 {nickname}. 错误: {db_err}"
|
||||
)
|
||||
|
|
@ -344,7 +361,6 @@ class NicknameManager:
|
|||
else:
|
||||
logger.debug(f"{log_prefix} LLM 未找到可靠的绰号映射或分析失败。")
|
||||
|
||||
|
||||
async def _call_llm_for_analysis(
|
||||
self,
|
||||
chat_history_str: str,
|
||||
|
|
@ -355,12 +371,12 @@ class NicknameManager:
|
|||
内部方法:调用 LLM 分析聊天记录和 Bot 回复,提取可靠的 用户ID-绰号 映射。
|
||||
(逻辑从 analyze_chat_for_nicknames 移入)
|
||||
"""
|
||||
if not self.llm_mapper: # 再次检查 LLM 映射器
|
||||
if not self.llm_mapper: # 再次检查 LLM 映射器
|
||||
logger.error("LLM 映射器未初始化,无法执行分析。")
|
||||
return {"is_exist": False}
|
||||
|
||||
prompt = _build_mapping_prompt(chat_history_str, bot_reply, user_name_map)
|
||||
logger.debug(f"构建的绰号映射 Prompt:\n{prompt[:500]}...") # 截断日志输出
|
||||
logger.debug(f"构建的绰号映射 Prompt:\n{prompt[:500]}...") # 截断日志输出
|
||||
|
||||
try:
|
||||
# 调用 LLM
|
||||
|
|
@ -379,17 +395,16 @@ class NicknameManager:
|
|||
response_content = match.group(1).strip()
|
||||
# 尝试直接解析 JSON,即使没有代码块标记
|
||||
elif response_content.startswith("{") and response_content.endswith("}"):
|
||||
pass # 可能是纯 JSON
|
||||
pass # 可能是纯 JSON
|
||||
else:
|
||||
# 尝试在文本中查找 JSON 对象
|
||||
json_match = re.search(r'\{.*\}', response_content, re.DOTALL)
|
||||
json_match = re.search(r"\{.*\}", response_content, re.DOTALL)
|
||||
if json_match:
|
||||
response_content = json_match.group(0)
|
||||
else:
|
||||
logger.warning(f"LLM 响应似乎不包含有效的 JSON 对象。响应: {response_content}")
|
||||
return {"is_exist": False}
|
||||
|
||||
|
||||
# 解析 JSON
|
||||
result = json.loads(response_content)
|
||||
|
||||
|
|
@ -404,7 +419,7 @@ class NicknameManager:
|
|||
original_data = result.get("data")
|
||||
if isinstance(original_data, dict) and original_data:
|
||||
logger.info(f"LLM 找到的原始绰号映射: {original_data}")
|
||||
filtered_data = self._filter_llm_results(original_data, user_name_map) # 调用过滤函数
|
||||
filtered_data = self._filter_llm_results(original_data, user_name_map) # 调用过滤函数
|
||||
if not filtered_data:
|
||||
logger.info("所有找到的绰号映射都被过滤掉了。")
|
||||
return {"is_exist": False}
|
||||
|
|
@ -418,7 +433,7 @@ class NicknameManager:
|
|||
elif is_exist is False:
|
||||
logger.info("LLM 明确指示未找到可靠的绰号映射 (is_exist=False)。")
|
||||
return {"is_exist": False}
|
||||
else: # is_exist 不是 True 或 False (包括 None)
|
||||
else: # is_exist 不是 True 或 False (包括 None)
|
||||
logger.warning(f"LLM 响应格式错误: 'is_exist' 的值 '{is_exist}' 无效。")
|
||||
return {"is_exist": False}
|
||||
|
||||
|
|
@ -432,7 +447,7 @@ class NicknameManager:
|
|||
def _filter_llm_results(self, original_data: Dict[str, str], user_name_map: Dict[str, str]) -> Dict[str, str]:
|
||||
"""过滤 LLM 返回的绰号映射结果。"""
|
||||
filtered_data = {}
|
||||
bot_qq_str = str(self.config.BOT_QQ) if hasattr(self.config, 'BOT_QQ') else None
|
||||
bot_qq_str = str(self.config.BOT_QQ) if hasattr(self.config, "BOT_QQ") else None
|
||||
|
||||
for user_id, nickname in original_data.items():
|
||||
# 过滤条件 1: user_id 必须是字符串
|
||||
|
|
@ -455,11 +470,10 @@ class NicknameManager:
|
|||
# continue
|
||||
|
||||
# 如果通过所有过滤条件,则保留
|
||||
filtered_data[user_id] = nickname.strip() # 保留时去除首尾空白
|
||||
filtered_data[user_id] = nickname.strip() # 保留时去除首尾空白
|
||||
|
||||
return filtered_data
|
||||
|
||||
|
||||
# 线程相关
|
||||
def _run_processor_in_thread(self):
|
||||
"""后台线程的入口函数,负责创建和运行 asyncio 事件循环。"""
|
||||
|
|
@ -497,7 +511,6 @@ class NicknameManager:
|
|||
logger.error(f"(线程 ID: {thread_id}) 关闭循环时出错: {loop_close_err}", exc_info=True)
|
||||
logger.info(f"绰号处理器线程结束 (线程 ID: {thread_id}).")
|
||||
|
||||
|
||||
async def _processing_loop(self):
|
||||
"""后台线程中运行的异步处理循环。"""
|
||||
thread_id = threading.get_ident()
|
||||
|
|
@ -511,14 +524,14 @@ class NicknameManager:
|
|||
# 处理获取到的项目
|
||||
await self._analyze_and_update_nicknames(item)
|
||||
|
||||
self.nickname_queue.task_done() # 标记任务完成
|
||||
self.nickname_queue.task_done() # 标记任务完成
|
||||
|
||||
except queue.Empty:
|
||||
# 超时,队列为空,继续循环检查停止事件
|
||||
continue
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"绰号处理循环被取消 (线程 ID: {thread_id})。")
|
||||
break # 任务被取消,退出循环
|
||||
break # 任务被取消,退出循环
|
||||
except Exception as e:
|
||||
# 捕获处理单个项目时可能发生的其他异常
|
||||
logger.error(f"(线程 ID: {thread_id}) 绰号处理循环出错: {e}\n{traceback.format_exc()}")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ logger = get_logger("nickname_mapper")
|
|||
|
||||
# LLMRequest 实例和 analyze_chat_for_nicknames 函数已被移除
|
||||
|
||||
|
||||
def _build_mapping_prompt(chat_history_str: str, bot_reply: str, user_name_map: Dict[str, str]) -> str:
|
||||
"""
|
||||
构建用于 LLM 进行绰号映射分析的 Prompt。
|
||||
|
|
@ -23,7 +24,7 @@ def _build_mapping_prompt(chat_history_str: str, bot_reply: str, user_name_map:
|
|||
# 将 user_name_map 格式化为列表字符串
|
||||
user_list_str = "\n".join([f"- {uid}: {name}" for uid, name in user_name_map.items() if uid and name])
|
||||
if not user_list_str:
|
||||
user_list_str = "无" # 如果映射为空,明确告知
|
||||
user_list_str = "无" # 如果映射为空,明确告知
|
||||
|
||||
# 核心 Prompt 内容
|
||||
prompt = f"""
|
||||
|
|
@ -73,4 +74,5 @@ def _build_mapping_prompt(chat_history_str: str, bot_reply: str, user_name_map:
|
|||
# logger.debug(f"构建的绰号映射 Prompt (部分):\n{prompt[:500]}...") # 可以在 NicknameManager 中记录
|
||||
return prompt
|
||||
|
||||
|
||||
# analyze_chat_for_nicknames 函数已被移除,其逻辑移至 NicknameManager._call_llm_for_analysis
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ def select_nicknames_for_prompt(all_nicknames_info: Dict[str, List[Dict[str, int
|
|||
if not all_nicknames_info:
|
||||
return []
|
||||
|
||||
candidates = [] # 存储 (用户名, 绰号, 次数, 权重)
|
||||
smoothing_factor = getattr(global_config, "NICKNAME_PROBABILITY_SMOOTHING", 1.0) # 平滑因子,避免权重为0
|
||||
candidates = [] # 存储 (用户名, 绰号, 次数, 权重)
|
||||
smoothing_factor = getattr(global_config, "NICKNAME_PROBABILITY_SMOOTHING", 1.0) # 平滑因子,避免权重为0
|
||||
|
||||
for user_name, nicknames in all_nicknames_info.items():
|
||||
if nicknames and isinstance(nicknames, list):
|
||||
|
|
@ -35,10 +35,12 @@ def select_nicknames_for_prompt(all_nicknames_info: Dict[str, List[Dict[str, int
|
|||
nickname, count = list(nickname_entry.items())[0]
|
||||
# 确保次数是正整数
|
||||
if isinstance(count, int) and count > 0 and isinstance(nickname, str) and nickname:
|
||||
weight = count + smoothing_factor # 计算权重
|
||||
weight = count + smoothing_factor # 计算权重
|
||||
candidates.append((user_name, nickname, count, weight))
|
||||
else:
|
||||
logger.warning(f"用户 '{user_name}' 的绰号条目无效: {nickname_entry} (次数非正整数或绰号为空)。已跳过。")
|
||||
logger.warning(
|
||||
f"用户 '{user_name}' 的绰号条目无效: {nickname_entry} (次数非正整数或绰号为空)。已跳过。"
|
||||
)
|
||||
else:
|
||||
logger.warning(f"用户 '{user_name}' 的绰号条目格式无效: {nickname_entry}。已跳过。")
|
||||
|
||||
|
|
@ -61,9 +63,9 @@ def select_nicknames_for_prompt(all_nicknames_info: Dict[str, List[Dict[str, int
|
|||
# 筛选出未被选中的候选
|
||||
selected_ids = set(
|
||||
(c[0], c[1]) for c in selected_candidates_with_weight
|
||||
) # 使用 (用户名, 绰号) 作为唯一标识
|
||||
) # 使用 (用户名, 绰号) 作为唯一标识
|
||||
remaining_candidates = [c for c in candidates if (c[0], c[1]) not in selected_ids]
|
||||
remaining_candidates.sort(key=lambda x: x[2], reverse=True) # 按原始次数排序
|
||||
remaining_candidates.sort(key=lambda x: x[2], reverse=True) # 按原始次数排序
|
||||
needed = num_to_select - len(selected_candidates_with_weight)
|
||||
selected_candidates_with_weight.extend(remaining_candidates[:needed])
|
||||
|
||||
|
|
@ -71,7 +73,7 @@ def select_nicknames_for_prompt(all_nicknames_info: Dict[str, List[Dict[str, int
|
|||
# 日志:记录加权随机选择时发生的错误,并回退到简单选择
|
||||
logger.error(f"绰号加权随机选择时出错: {e}。将回退到选择次数最多的 Top N。", exc_info=True)
|
||||
# 出错时回退到选择次数最多的 N 个
|
||||
candidates.sort(key=lambda x: x[2], reverse=True) # 按原始次数排序
|
||||
candidates.sort(key=lambda x: x[2], reverse=True) # 按原始次数排序
|
||||
selected_candidates_with_weight = candidates[:num_to_select]
|
||||
|
||||
# 格式化输出结果为 (用户名, 绰号, 次数),移除权重
|
||||
|
|
@ -98,10 +100,8 @@ def format_nickname_prompt_injection(selected_nicknames: List[Tuple[str, str, in
|
|||
return ""
|
||||
|
||||
# Prompt 注入部分的标题
|
||||
prompt_lines = [
|
||||
"以下是聊天记录中一些成员在本群的绰号信息(按常用度排序),供你参考:"
|
||||
]
|
||||
grouped_by_user: Dict[str, List[str]] = {} # 用于按用户分组
|
||||
prompt_lines = ["以下是聊天记录中一些成员在本群的绰号信息(按常用度排序),供你参考:"]
|
||||
grouped_by_user: Dict[str, List[str]] = {} # 用于按用户分组
|
||||
|
||||
# 按用户分组绰号
|
||||
for user_name, nickname, _count in selected_nicknames:
|
||||
|
|
@ -112,7 +112,7 @@ def format_nickname_prompt_injection(selected_nicknames: List[Tuple[str, str, in
|
|||
|
||||
# 构建每个用户的绰号字符串
|
||||
for user_name, nicknames in grouped_by_user.items():
|
||||
nicknames_str = "、".join(nicknames) # 使用中文顿号连接
|
||||
nicknames_str = "、".join(nicknames) # 使用中文顿号连接
|
||||
# 格式化输出,例如: "- 张三,ta 可能被称为:“三儿”、“张哥”"
|
||||
prompt_lines.append(f"- {user_name},ta 可能被称为:{nicknames_str}")
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ def weighted_sample_without_replacement(
|
|||
return []
|
||||
n = len(candidates)
|
||||
if k >= n:
|
||||
return candidates[:] # 返回副本
|
||||
return candidates[:] # 返回副本
|
||||
|
||||
# 计算每个元素的 key = U^(1/weight),其中 U 是 (0, 1) 之间的随机数
|
||||
# 为了数值稳定性,计算 log(key) = log(U) / weight
|
||||
|
|
@ -151,12 +151,12 @@ def weighted_sample_without_replacement(
|
|||
weight = candidates[i][3]
|
||||
if weight <= 0:
|
||||
# 处理权重为0或负数的情况,赋予一个极小的概率(或极大负数的log_key)
|
||||
log_key = float('-inf') # 或者一个非常大的负数
|
||||
log_key = float("-inf") # 或者一个非常大的负数
|
||||
logger.warning(f"候选者 {candidates[i][:2]} 的权重为非正数 ({weight}),抽中概率极低。")
|
||||
else:
|
||||
log_u = -random.expovariate(1.0) # 生成 -Exponential(1) 随机数
|
||||
log_u = -random.expovariate(1.0) # 生成 -Exponential(1) 随机数
|
||||
log_key = log_u / weight
|
||||
weighted_keys.append((log_key, i)) # 存储 (log_key, 原始索引)
|
||||
weighted_keys.append((log_key, i)) # 存储 (log_key, 原始索引)
|
||||
|
||||
# 按 log_key 降序排序 (相当于按 key 升序排序)
|
||||
weighted_keys.sort(key=lambda x: x[0], reverse=True)
|
||||
|
|
@ -169,6 +169,7 @@ def weighted_sample_without_replacement(
|
|||
|
||||
return selected_items
|
||||
|
||||
|
||||
# 移除旧的流程函数
|
||||
# get_nickname_injection_for_prompt 和 trigger_nickname_analysis_if_needed
|
||||
# 的逻辑现在由 NicknameManager 处理
|
||||
|
|
|
|||
|
|
@ -873,7 +873,9 @@ class HeartFChatting:
|
|||
limit=global_config.observation_context_size, # 使用与 prompt 构建一致的 limit
|
||||
)
|
||||
# 调用工具函数获取格式化后的绰号字符串
|
||||
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(self.chat_stream, message_list_before_now)
|
||||
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(
|
||||
self.chat_stream, message_list_before_now
|
||||
)
|
||||
|
||||
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
|
||||
prompt = await prompt_builder.build_planner_prompt(
|
||||
|
|
|
|||
|
|
@ -255,7 +255,9 @@ async def _build_prompt_focus(reason, current_mind_info, structured_info, chat_s
|
|||
chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2")
|
||||
|
||||
# 调用新的工具函数获取绰号信息
|
||||
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(chat_stream, message_list_before_now)
|
||||
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(
|
||||
chat_stream, message_list_before_now
|
||||
)
|
||||
|
||||
prompt = await global_prompt_manager.format_prompt(
|
||||
template_name,
|
||||
|
|
@ -451,7 +453,9 @@ class PromptBuilder:
|
|||
chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2")
|
||||
|
||||
# 调用新的工具函数获取绰号信息
|
||||
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(chat_stream, message_list_before_now)
|
||||
nickname_injection_str = await nickname_manager.get_nickname_prompt_injection(
|
||||
chat_stream, message_list_before_now
|
||||
)
|
||||
|
||||
prompt = await global_prompt_manager.format_prompt(
|
||||
template_name,
|
||||
|
|
|
|||
Loading…
Reference in New Issue