This commit is contained in:
SengokuCola
2025-05-01 01:41:30 +08:00
parent 0b62802c2c
commit ccbdc6ffe0
4 changed files with 121 additions and 115 deletions

View File

@@ -633,15 +633,12 @@ HFC_STYLE_CONFIG = {
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}",
}, },
"simple": { "simple": {
"console_format": ( "console_format": ("<level>{time:MM-DD HH:mm}</level> | <light-green>专注聊天 | {message}</light-green>"),
"<level>{time:MM-DD HH:mm}</level> | <light-green>专注聊天 | {message}</light-green>"
),
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}",
}, },
} }
CONFIRM_STYLE_CONFIG = { CONFIRM_STYLE_CONFIG = {
"console_format": "<RED>{message}</RED>", # noqa: E501 "console_format": "<RED>{message}</RED>", # noqa: E501
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | EULA与PRIVACY确认 | {message}", "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | EULA与PRIVACY确认 | {message}",

View File

@@ -37,9 +37,9 @@ class MaiEmoji:
def __init__(self, full_path: str): def __init__(self, full_path: str):
if not full_path: if not full_path:
raise ValueError("full_path cannot be empty") raise ValueError("full_path cannot be empty")
self.full_path = full_path # 文件的完整路径 (包括文件名) self.full_path = full_path # 文件的完整路径 (包括文件名)
self.path = os.path.dirname(full_path) # 文件所在的目录路径 self.path = os.path.dirname(full_path) # 文件所在的目录路径
self.filename = os.path.basename(full_path) # 文件名 self.filename = os.path.basename(full_path) # 文件名
self.embedding = [] self.embedding = []
self.hash = "" # 初始为空,在创建实例时会计算 self.hash = "" # 初始为空,在创建实例时会计算
self.description = "" self.description = ""
@@ -92,13 +92,13 @@ class MaiEmoji:
return True return True
except FileNotFoundError: except FileNotFoundError:
logger.error(f"[初始化错误] 文件在处理过程中丢失: {self.full_path}") logger.error(f"[初始化错误] 文件在处理过程中丢失: {self.full_path}")
self.is_deleted = True self.is_deleted = True
return None return None
except base64.binascii.Error as b64_error: except base64.binascii.Error as b64_error:
logger.error(f"[初始化错误] Base64解码失败 ({self.filename}): {b64_error}") logger.error(f"[初始化错误] Base64解码失败 ({self.filename}): {b64_error}")
self.is_deleted = True self.is_deleted = True
return None return None
except Exception as e: except Exception as e:
logger.error(f"[初始化错误] 初始化表情包时发生未预期错误 ({self.filename}): {str(e)}") logger.error(f"[初始化错误] 初始化表情包时发生未预期错误 ({self.filename}): {str(e)}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
@@ -146,8 +146,8 @@ class MaiEmoji:
# 准备数据库记录 for emoji collection # 准备数据库记录 for emoji collection
emoji_record = { emoji_record = {
"filename": self.filename, "filename": self.filename,
"path": self.path, # 存储目录路径 "path": self.path, # 存储目录路径
"full_path": self.full_path, # 存储完整文件路径 "full_path": self.full_path, # 存储完整文件路径
"embedding": self.embedding, "embedding": self.embedding,
"description": self.description, "description": self.description,
"emotion": self.emotion, "emotion": self.emotion,
@@ -170,11 +170,11 @@ class MaiEmoji:
# 数据库保存失败,是否需要将文件移回?为了简化,暂时只记录错误 # 数据库保存失败,是否需要将文件移回?为了简化,暂时只记录错误
# 可以考虑在这里尝试删除已移动的文件,避免残留 # 可以考虑在这里尝试删除已移动的文件,避免残留
try: try:
if os.path.exists(self.full_path): # full_path 此时是目标路径 if os.path.exists(self.full_path): # full_path 此时是目标路径
os.remove(self.full_path) os.remove(self.full_path)
logger.warning(f"[回滚] 已删除移动失败后残留的文件: {self.full_path}") logger.warning(f"[回滚] 已删除移动失败后残留的文件: {self.full_path}")
except Exception as remove_error: except Exception as remove_error:
logger.error(f"[错误] 回滚删除文件失败: {remove_error}") logger.error(f"[错误] 回滚删除文件失败: {remove_error}")
return False return False
except Exception as e: except Exception as e:
@@ -213,9 +213,11 @@ class MaiEmoji:
else: else:
# 如果数据库记录删除失败,但文件可能已删除,记录一个警告 # 如果数据库记录删除失败,但文件可能已删除,记录一个警告
if not os.path.exists(file_to_delete): if not os.path.exists(file_to_delete):
logger.warning(f"[警告] 表情包文件 {file_to_delete} 已删除,但数据库记录删除失败 (Hash: {self.hash})") logger.warning(
f"[警告] 表情包文件 {file_to_delete} 已删除,但数据库记录删除失败 (Hash: {self.hash})"
)
else: else:
logger.error(f"[错误] 删除表情包数据库记录失败: {self.hash}") logger.error(f"[错误] 删除表情包数据库记录失败: {self.hash}")
return False return False
except Exception as e: except Exception as e:
@@ -323,7 +325,7 @@ class EmojiManager:
# 计算每个表情包与输入文本的最大情感相似度 # 计算每个表情包与输入文本的最大情感相似度
emoji_similarities = [] emoji_similarities = []
for emoji in all_emojis: for emoji in all_emojis:
# 跳过已标记为删除的对象 # 跳过已标记为删除的对象
if emoji.is_deleted: if emoji.is_deleted:
continue continue
@@ -421,17 +423,17 @@ class EmojiManager:
objects_to_remove = [] objects_to_remove = []
for emoji in self.emoji_objects: for emoji in self.emoji_objects:
try: try:
# 跳过已经标记为删除的,避免重复处理 # 跳过已经标记为删除的,避免重复处理
if emoji.is_deleted: if emoji.is_deleted:
objects_to_remove.append(emoji) # 收集起来一次性移除 objects_to_remove.append(emoji) # 收集起来一次性移除
continue continue
# 检查文件是否存在 # 检查文件是否存在
if not os.path.exists(emoji.full_path): if not os.path.exists(emoji.full_path):
logger.warning(f"[检查] 表情包文件丢失: {emoji.full_path}") logger.warning(f"[检查] 表情包文件丢失: {emoji.full_path}")
# 执行表情包对象的删除方法 # 执行表情包对象的删除方法
await emoji.delete() # delete 方法现在会标记 is_deleted await emoji.delete() # delete 方法现在会标记 is_deleted
objects_to_remove.append(emoji) # 标记删除后,也收集起来移除 objects_to_remove.append(emoji) # 标记删除后,也收集起来移除
# 更新计数 # 更新计数
self.emoji_num -= 1 self.emoji_num -= 1
removed_count += 1 removed_count += 1
@@ -453,7 +455,7 @@ class EmojiManager:
# 从 self.emoji_objects 中移除标记的对象 # 从 self.emoji_objects 中移除标记的对象
if objects_to_remove: if objects_to_remove:
self.emoji_objects = [e for e in self.emoji_objects if e not in objects_to_remove] self.emoji_objects = [e for e in self.emoji_objects if e not in objects_to_remove]
# 清理 EMOJI_REGISTED_DIR 目录中未被追踪的文件 # 清理 EMOJI_REGISTED_DIR 目录中未被追踪的文件
await self.clean_unused_emojis(EMOJI_REGISTED_DIR, self.emoji_objects) await self.clean_unused_emojis(EMOJI_REGISTED_DIR, self.emoji_objects)
@@ -538,7 +540,7 @@ class EmojiManager:
if not full_path: if not full_path:
logger.warning(f"[加载错误] 数据库记录缺少 'full_path' 字段: {emoji_data.get('_id')}") logger.warning(f"[加载错误] 数据库记录缺少 'full_path' 字段: {emoji_data.get('_id')}")
load_errors += 1 load_errors += 1
continue # 跳过缺少 full_path 的记录 continue # 跳过缺少 full_path 的记录
try: try:
# 使用 full_path 初始化 MaiEmoji 对象 # 使用 full_path 初始化 MaiEmoji 对象
@@ -548,9 +550,9 @@ class EmojiManager:
emoji.hash = emoji_data.get("hash", "") emoji.hash = emoji_data.get("hash", "")
# 如果 hash 为空,也跳过?取决于业务逻辑 # 如果 hash 为空,也跳过?取决于业务逻辑
if not emoji.hash: if not emoji.hash:
logger.warning(f"[加载错误] 数据库记录缺少 'hash' 字段: {full_path}") logger.warning(f"[加载错误] 数据库记录缺少 'hash' 字段: {full_path}")
load_errors += 1 load_errors += 1
continue continue
emoji.description = emoji_data.get("description", "") emoji.description = emoji_data.get("description", "")
emoji.emotion = emoji_data.get("emotion", []) emoji.emotion = emoji_data.get("emotion", [])
@@ -558,21 +560,22 @@ class EmojiManager:
# 优先使用 last_used_time否则用 timestamp最后用当前时间 # 优先使用 last_used_time否则用 timestamp最后用当前时间
last_used = emoji_data.get("last_used_time") last_used = emoji_data.get("last_used_time")
timestamp = emoji_data.get("timestamp") timestamp = emoji_data.get("timestamp")
emoji.last_used_time = last_used if last_used is not None else (timestamp if timestamp is not None else time.time()) emoji.last_used_time = (
last_used if last_used is not None else (timestamp if timestamp is not None else time.time())
)
emoji.register_time = timestamp if timestamp is not None else time.time() emoji.register_time = timestamp if timestamp is not None else time.time()
emoji.format = emoji_data.get("format", "") # 加载格式 emoji.format = emoji_data.get("format", "") # 加载格式
# 不需要再手动设置 path 和 filename__init__ 会自动处理 # 不需要再手动设置 path 和 filename__init__ 会自动处理
emoji_objects.append(emoji) emoji_objects.append(emoji)
except ValueError as ve: #捕获 __init__ 可能的错误 except ValueError as ve: # 捕获 __init__ 可能的错误
logger.error(f"[加载错误] 初始化 MaiEmoji 失败 ({full_path}): {ve}") logger.error(f"[加载错误] 初始化 MaiEmoji 失败 ({full_path}): {ve}")
load_errors += 1 load_errors += 1
except Exception as e: except Exception as e:
logger.error(f"[加载错误] 处理数据库记录时出错 ({full_path}): {str(e)}") logger.error(f"[加载错误] 处理数据库记录时出错 ({full_path}): {str(e)}")
load_errors += 1 load_errors += 1
# 更新内存中的列表和数量 # 更新内存中的列表和数量
self.emoji_objects = emoji_objects self.emoji_objects = emoji_objects
@@ -580,12 +583,11 @@ class EmojiManager:
logger.success(f"[数据库] 加载完成: 共加载 {self.emoji_num} 个表情包记录。") logger.success(f"[数据库] 加载完成: 共加载 {self.emoji_num} 个表情包记录。")
if load_errors > 0: if load_errors > 0:
logger.warning(f"[数据库] 加载过程中出现 {load_errors} 个错误。") logger.warning(f"[数据库] 加载过程中出现 {load_errors} 个错误。")
except Exception as e: except Exception as e:
logger.error(f"[错误] 从数据库加载所有表情包对象失败: {str(e)}") logger.error(f"[错误] 从数据库加载所有表情包对象失败: {str(e)}")
self.emoji_objects = [] # 加载失败则清空列表 self.emoji_objects = [] # 加载失败则清空列表
self.emoji_num = 0 self.emoji_num = 0
async def get_emoji_from_db(self, hash=None): async def get_emoji_from_db(self, hash=None):
@@ -604,8 +606,9 @@ class EmojiManager:
if hash: if hash:
query = {"hash": hash} query = {"hash": hash}
else: else:
logger.warning("[查询] 未提供 hash将尝试加载所有表情包建议使用 get_all_emoji_from_db 更新管理器状态。") logger.warning(
"[查询] 未提供 hash将尝试加载所有表情包建议使用 get_all_emoji_from_db 更新管理器状态。"
)
emoji_data_list = list(db.emoji.find(query)) emoji_data_list = list(db.emoji.find(query))
emoji_objects = [] emoji_objects = []
@@ -622,29 +625,30 @@ class EmojiManager:
emoji = MaiEmoji(full_path=full_path) emoji = MaiEmoji(full_path=full_path)
emoji.hash = emoji_data.get("hash", "") emoji.hash = emoji_data.get("hash", "")
if not emoji.hash: if not emoji.hash:
logger.warning(f"[加载错误] 数据库记录缺少 'hash' 字段: {full_path}") logger.warning(f"[加载错误] 数据库记录缺少 'hash' 字段: {full_path}")
load_errors += 1 load_errors += 1
continue continue
emoji.description = emoji_data.get("description", "") emoji.description = emoji_data.get("description", "")
emoji.emotion = emoji_data.get("emotion", []) emoji.emotion = emoji_data.get("emotion", [])
emoji.usage_count = emoji_data.get("usage_count", 0) emoji.usage_count = emoji_data.get("usage_count", 0)
last_used = emoji_data.get("last_used_time") last_used = emoji_data.get("last_used_time")
timestamp = emoji_data.get("timestamp") timestamp = emoji_data.get("timestamp")
emoji.last_used_time = last_used if last_used is not None else (timestamp if timestamp is not None else time.time()) emoji.last_used_time = (
last_used if last_used is not None else (timestamp if timestamp is not None else time.time())
)
emoji.register_time = timestamp if timestamp is not None else time.time() emoji.register_time = timestamp if timestamp is not None else time.time()
emoji.format = emoji_data.get("format", "") emoji.format = emoji_data.get("format", "")
emoji_objects.append(emoji) emoji_objects.append(emoji)
except ValueError as ve: except ValueError as ve:
logger.error(f"[加载错误] 初始化 MaiEmoji 失败 ({full_path}): {ve}") logger.error(f"[加载错误] 初始化 MaiEmoji 失败 ({full_path}): {ve}")
load_errors += 1 load_errors += 1
except Exception as e: except Exception as e:
logger.error(f"[加载错误] 处理数据库记录时出错 ({full_path}): {str(e)}") logger.error(f"[加载错误] 处理数据库记录时出错 ({full_path}): {str(e)}")
load_errors += 1 load_errors += 1
if load_errors > 0: if load_errors > 0:
logger.warning(f"[查询] 加载过程中出现 {load_errors} 个错误。") logger.warning(f"[查询] 加载过程中出现 {load_errors} 个错误。")
return emoji_objects return emoji_objects
@@ -661,10 +665,10 @@ class EmojiManager:
MaiEmoji 或 None: 如果找到则返回 MaiEmoji 对象,否则返回 None MaiEmoji 或 None: 如果找到则返回 MaiEmoji 对象,否则返回 None
""" """
for emoji in self.emoji_objects: for emoji in self.emoji_objects:
# 确保对象未被标记为删除且哈希值匹配 # 确保对象未被标记为删除且哈希值匹配
if not emoji.is_deleted and emoji.hash == hash: if not emoji.is_deleted and emoji.hash == hash:
return emoji return emoji
return None # 如果循环结束还没找到,则返回 None return None # 如果循环结束还没找到,则返回 None
async def delete_emoji(self, emoji_hash: str) -> bool: async def delete_emoji(self, emoji_hash: str) -> bool:
"""根据哈希值删除表情包 """根据哈希值删除表情包
@@ -886,14 +890,14 @@ class EmojiManager:
""" """
file_full_path = os.path.join(EMOJI_DIR, filename) file_full_path = os.path.join(EMOJI_DIR, filename)
if not os.path.exists(file_full_path): if not os.path.exists(file_full_path):
logger.error(f"[注册失败] 文件不存在: {file_full_path}") logger.error(f"[注册失败] 文件不存在: {file_full_path}")
return False return False
try: try:
# 1. 创建 MaiEmoji 实例并初始化哈希和格式 # 1. 创建 MaiEmoji 实例并初始化哈希和格式
new_emoji = MaiEmoji(full_path=file_full_path) new_emoji = MaiEmoji(full_path=file_full_path)
init_result = await new_emoji.initialize_hash_format() init_result = await new_emoji.initialize_hash_format()
if init_result is None or new_emoji.is_deleted: # 初始化失败或文件读取错误 if init_result is None or new_emoji.is_deleted: # 初始化失败或文件读取错误
logger.error(f"[注册失败] 初始化哈希和格式失败: {filename}") logger.error(f"[注册失败] 初始化哈希和格式失败: {filename}")
# 是否需要删除源文件?看业务需求,暂时不删 # 是否需要删除源文件?看业务需求,暂时不删
return False return False
@@ -901,22 +905,22 @@ class EmojiManager:
# 2. 检查哈希是否已存在 (在内存中检查) # 2. 检查哈希是否已存在 (在内存中检查)
if await self.get_emoji_from_manager(new_emoji.hash): if await self.get_emoji_from_manager(new_emoji.hash):
logger.warning(f"[注册跳过] 表情包已存在 (Hash: {new_emoji.hash}): {filename}") logger.warning(f"[注册跳过] 表情包已存在 (Hash: {new_emoji.hash}): {filename}")
# 删除重复的源文件 # 删除重复的源文件
try: try:
os.remove(file_full_path) os.remove(file_full_path)
logger.info(f"[清理] 删除重复的待注册文件: {filename}") logger.info(f"[清理] 删除重复的待注册文件: {filename}")
except Exception as e: except Exception as e:
logger.error(f"[错误] 删除重复文件失败: {str(e)}") logger.error(f"[错误] 删除重复文件失败: {str(e)}")
return False # 返回 False 表示未注册新表情 return False # 返回 False 表示未注册新表情
# 3. 构建描述和情感 # 3. 构建描述和情感
try: try:
emoji_base64 = image_path_to_base64(file_full_path) emoji_base64 = image_path_to_base64(file_full_path)
if emoji_base64 is None: # 再次检查读取 if emoji_base64 is None: # 再次检查读取
logger.error(f"[注册失败] 无法读取图片以生成描述: {filename}") logger.error(f"[注册失败] 无法读取图片以生成描述: {filename}")
return False return False
description, emotions = await self.build_emoji_description(emoji_base64) description, emotions = await self.build_emoji_description(emoji_base64)
if not description: # 检查描述是否成功生成或审核通过 if not description: # 检查描述是否成功生成或审核通过
logger.warning(f"[注册失败] 未能生成有效描述或审核未通过: {filename}") logger.warning(f"[注册失败] 未能生成有效描述或审核未通过: {filename}")
# 删除未能生成描述的文件 # 删除未能生成描述的文件
try: try:
@@ -943,9 +947,9 @@ class EmojiManager:
replaced = await self.replace_a_emoji(new_emoji) replaced = await self.replace_a_emoji(new_emoji)
if not replaced: if not replaced:
logger.error("[注册失败] 替换表情包失败,无法完成注册") logger.error("[注册失败] 替换表情包失败,无法完成注册")
# 替换失败,删除新表情包文件 # 替换失败,删除新表情包文件
try: try:
os.remove(file_full_path) # new_emoji 的 full_path 此时还是源路径 os.remove(file_full_path) # new_emoji 的 full_path 此时还是源路径
logger.info(f"[清理] 删除替换失败的新表情文件: {filename}") logger.info(f"[清理] 删除替换失败的新表情文件: {filename}")
except Exception as e: except Exception as e:
logger.error(f"[错误] 删除替换失败文件时出错: {str(e)}") logger.error(f"[错误] 删除替换失败文件时出错: {str(e)}")
@@ -954,7 +958,7 @@ class EmojiManager:
return True return True
else: else:
# 直接注册 # 直接注册
register_success = await new_emoji.register_to_db() # 此方法会移动文件并更新 DB register_success = await new_emoji.register_to_db() # 此方法会移动文件并更新 DB
if register_success: if register_success:
# 注册成功后,添加到内存列表 # 注册成功后,添加到内存列表
self.emoji_objects.append(new_emoji) self.emoji_objects.append(new_emoji)
@@ -963,20 +967,20 @@ class EmojiManager:
return True return True
else: else:
logger.error(f"[注册失败] 保存表情包到数据库/移动文件失败: {filename}") logger.error(f"[注册失败] 保存表情包到数据库/移动文件失败: {filename}")
# register_to_db 失败时,内部会尝试清理移动后的文件,源文件可能还在 # register_to_db 失败时,内部会尝试清理移动后的文件,源文件可能还在
# 是否需要删除源文件? # 是否需要删除源文件?
if os.path.exists(file_full_path): if os.path.exists(file_full_path):
try: try:
os.remove(file_full_path) os.remove(file_full_path)
logger.info(f"[清理] 删除注册失败的源文件: {filename}") logger.info(f"[清理] 删除注册失败的源文件: {filename}")
except Exception as e: except Exception as e:
logger.error(f"[错误] 删除注册失败源文件时出错: {str(e)}") logger.error(f"[错误] 删除注册失败源文件时出错: {str(e)}")
return False return False
except Exception as e: except Exception as e:
logger.error(f"[错误] 注册表情包时发生未预期错误 ({filename}): {str(e)}") logger.error(f"[错误] 注册表情包时发生未预期错误 ({filename}): {str(e)}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
# 尝试删除源文件以避免循环处理 # 尝试删除源文件以避免循环处理
if os.path.exists(file_full_path): if os.path.exists(file_full_path):
try: try:
os.remove(file_full_path) os.remove(file_full_path)

View File

@@ -15,9 +15,7 @@ from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move
from src.plugins.utils.timer_calculator import Timer # <--- Import Timer from src.plugins.utils.timer_calculator import Timer # <--- Import Timer
from src.do_tool.tool_use import ToolUser
from src.plugins.emoji_system.emoji_manager import emoji_manager from src.plugins.emoji_system.emoji_manager import emoji_manager
from src.plugins.utils.json_utils import process_llm_tool_calls, extract_tool_call_arguments
from src.heart_flow.sub_mind import SubMind from src.heart_flow.sub_mind import SubMind
from src.heart_flow.observation import Observation from src.heart_flow.observation import Observation
from src.plugins.heartFC_chat.heartflow_prompt_builder import global_prompt_manager, prompt_builder from src.plugins.heartFC_chat.heartflow_prompt_builder import global_prompt_manager, prompt_builder
@@ -788,7 +786,9 @@ class HeartFChatting:
logger.info(f"{self.log_prefix}[Planner] 连续回复 2 次80% 概率移除 text_reply 和 emoji_reply (触发)") logger.info(f"{self.log_prefix}[Planner] 连续回复 2 次80% 概率移除 text_reply 和 emoji_reply (触发)")
actions_to_remove_temporarily.extend(["text_reply", "emoji_reply"]) actions_to_remove_temporarily.extend(["text_reply", "emoji_reply"])
else: else:
logger.info(f"{self.log_prefix}[Planner] 连续回复 2 次80% 概率移除 text_reply 和 emoji_reply (未触发)") logger.info(
f"{self.log_prefix}[Planner] 连续回复 2 次80% 概率移除 text_reply 和 emoji_reply (未触发)"
)
elif lian_xu_wen_ben_hui_fu == 1: elif lian_xu_wen_ben_hui_fu == 1:
if probability_roll < 0.4: if probability_roll < 0.4:
logger.info(f"{self.log_prefix}[Planner] 连续回复 1 次40% 概率移除 text_reply (触发)") logger.info(f"{self.log_prefix}[Planner] 连续回复 1 次40% 概率移除 text_reply (触发)")
@@ -805,10 +805,10 @@ class HeartFChatting:
observed_messages_str = observation.talking_message_str_truncate observed_messages_str = observation.talking_message_str_truncate
# --- 使用 LLM 进行决策 (JSON 输出模式) --- # # --- 使用 LLM 进行决策 (JSON 输出模式) --- #
action = "no_reply" # 默认动作 action = "no_reply" # 默认动作
reasoning = "规划器初始化默认" reasoning = "规划器初始化默认"
emoji_query = "" emoji_query = ""
llm_error = False # LLM 请求或解析错误标志 llm_error = False # LLM 请求或解析错误标志
# 获取我们将传递给 prompt 构建器和用于验证的当前可用动作 # 获取我们将传递给 prompt 构建器和用于验证的当前可用动作
current_available_actions = self.action_manager.get_available_actions() current_available_actions = self.action_manager.get_available_actions()
@@ -833,8 +833,8 @@ class HeartFChatting:
observed_messages_str, observed_messages_str,
current_mind, current_mind,
self.sub_mind.structured_info, self.sub_mind.structured_info,
"", # replan_prompt_str, "", # replan_prompt_str,
current_available_actions # <--- 传入当前可用动作 current_available_actions, # <--- 传入当前可用动作
) )
# --- 调用 LLM (普通文本生成) --- # --- 调用 LLM (普通文本生成) ---
@@ -851,8 +851,8 @@ class HeartFChatting:
reasoning = f"LLM 请求失败: {req_e}" reasoning = f"LLM 请求失败: {req_e}"
llm_error = True llm_error = True
# 直接使用默认动作返回错误结果 # 直接使用默认动作返回错误结果
action = "no_reply" # 明确设置为默认值 action = "no_reply" # 明确设置为默认值
emoji_query = "" # 明确设置为空 emoji_query = "" # 明确设置为空
# 不再立即返回,而是继续执行 finally 块以恢复动作 # 不再立即返回,而是继续执行 finally 块以恢复动作
# return { ... } # return { ... }
@@ -860,9 +860,11 @@ class HeartFChatting:
if not llm_error and llm_content: if not llm_error and llm_content:
try: try:
# 尝试去除可能的 markdown 代码块标记 # 尝试去除可能的 markdown 代码块标记
cleaned_content = llm_content.strip().removeprefix("```json").removeprefix("```").removesuffix("```").strip() cleaned_content = (
llm_content.strip().removeprefix("```json").removeprefix("```").removesuffix("```").strip()
)
if not cleaned_content: if not cleaned_content:
raise json.JSONDecodeError("Cleaned content is empty", cleaned_content, 0) raise json.JSONDecodeError("Cleaned content is empty", cleaned_content, 0)
parsed_json = json.loads(cleaned_content) parsed_json = json.loads(cleaned_content)
# 提取决策,提供默认值 # 提取决策,提供默认值
@@ -881,28 +883,32 @@ class HeartFChatting:
emoji_query = "" emoji_query = ""
# 检查 no_reply 是否也恰好被移除了 (极端情况) # 检查 no_reply 是否也恰好被移除了 (极端情况)
if "no_reply" not in current_available_actions: if "no_reply" not in current_available_actions:
logger.error(f"{self.log_prefix}[Planner] 严重错误:'no_reply' 动作也不可用!无法执行任何动作。") logger.error(
action = "error" # 回退到错误状态 f"{self.log_prefix}[Planner] 严重错误:'no_reply' 动作也不可用!无法执行任何动作。"
reasoning = "无法执行任何有效动作,包括 no_reply" )
llm_error = True # 标记为严重错误 action = "error" # 回退到错误状态
reasoning = "无法执行任何有效动作,包括 no_reply"
llm_error = True # 标记为严重错误
else: else:
llm_error = False # 视为逻辑修正而非 LLM 错误 llm_error = False # 视为逻辑修正而非 LLM 错误
else: else:
# 动作有效且可用 # 动作有效且可用
action = extracted_action action = extracted_action
reasoning = extracted_reasoning reasoning = extracted_reasoning
emoji_query = extracted_emoji_query emoji_query = extracted_emoji_query
llm_error = False # 解析成功 llm_error = False # 解析成功
logger.debug( logger.debug(
f"{self.log_prefix}[要做什么]\nPrompt:\n{prompt}\n\n决策结果 (来自JSON): {action}, 理由: {reasoning}, 表情查询: '{emoji_query}'" f"{self.log_prefix}[要做什么]\nPrompt:\n{prompt}\n\n决策结果 (来自JSON): {action}, 理由: {reasoning}, 表情查询: '{emoji_query}'"
) )
except json.JSONDecodeError as json_e: except json.JSONDecodeError as json_e:
logger.warning(f"{self.log_prefix}[Planner] 解析LLM响应JSON失败: {json_e}. LLM原始输出: '{llm_content}'") logger.warning(
f"{self.log_prefix}[Planner] 解析LLM响应JSON失败: {json_e}. LLM原始输出: '{llm_content}'"
)
reasoning = f"解析LLM响应JSON失败: {json_e}. 将使用默认动作 'no_reply'." reasoning = f"解析LLM响应JSON失败: {json_e}. 将使用默认动作 'no_reply'."
action = "no_reply" # 解析失败则默认不回复 action = "no_reply" # 解析失败则默认不回复
emoji_query = "" emoji_query = ""
llm_error = True # 标记解析错误 llm_error = True # 标记解析错误
except Exception as parse_e: except Exception as parse_e:
logger.error(f"{self.log_prefix}[Planner] 处理LLM响应时发生意外错误: {parse_e}") logger.error(f"{self.log_prefix}[Planner] 处理LLM响应时发生意外错误: {parse_e}")
reasoning = f"处理LLM响应时发生意外错误: {parse_e}. 将使用默认动作 'no_reply'." reasoning = f"处理LLM响应时发生意外错误: {parse_e}. 将使用默认动作 'no_reply'."
@@ -910,12 +916,12 @@ class HeartFChatting:
emoji_query = "" emoji_query = ""
llm_error = True llm_error = True
elif not llm_error and not llm_content: elif not llm_error and not llm_content:
# LLM 请求成功但返回空内容 # LLM 请求成功但返回空内容
logger.warning(f"{self.log_prefix}[Planner] LLM 返回了空内容。") logger.warning(f"{self.log_prefix}[Planner] LLM 返回了空内容。")
reasoning = "LLM 返回了空内容,使用默认动作 'no_reply'." reasoning = "LLM 返回了空内容,使用默认动作 'no_reply'."
action = "no_reply" action = "no_reply"
emoji_query = "" emoji_query = ""
llm_error = True # 标记为空响应错误 llm_error = True # 标记为空响应错误
# 如果 llm_error 在此阶段为 True意味着请求成功但解析失败或返回空 # 如果 llm_error 在此阶段为 True意味着请求成功但解析失败或返回空
# 如果 llm_error 在请求阶段就为 True则跳过了此解析块 # 如果 llm_error 在请求阶段就为 True则跳过了此解析块
@@ -923,7 +929,7 @@ class HeartFChatting:
except Exception as outer_e: except Exception as outer_e:
logger.error(f"{self.log_prefix}[Planner] Planner 处理过程中发生意外错误: {outer_e}") logger.error(f"{self.log_prefix}[Planner] Planner 处理过程中发生意外错误: {outer_e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
action = "error" # 发生未知错误,标记为 error 动作 action = "error" # 发生未知错误,标记为 error 动作
reasoning = f"Planner 内部处理错误: {outer_e}" reasoning = f"Planner 内部处理错误: {outer_e}"
emoji_query = "" emoji_query = ""
llm_error = True llm_error = True
@@ -944,7 +950,7 @@ class HeartFChatting:
logger.info( logger.info(
f"{self.log_prefix}[Planner] 但是麦麦这次不想加表情 ({1 - EMOJI_SEND_PRO:.0%}),忽略表情 '{emoji_query}'" f"{self.log_prefix}[Planner] 但是麦麦这次不想加表情 ({1 - EMOJI_SEND_PRO:.0%}),忽略表情 '{emoji_query}'"
) )
emoji_query = "" # 清空表情请求 emoji_query = "" # 清空表情请求
else: else:
logger.info(f"{self.log_prefix}[Planner] 好吧,加上表情 '{emoji_query}'") logger.info(f"{self.log_prefix}[Planner] 好吧,加上表情 '{emoji_query}'")
# --- 结束概率性忽略 --- # --- 结束概率性忽略 ---
@@ -956,7 +962,7 @@ class HeartFChatting:
"emoji_query": emoji_query, "emoji_query": emoji_query,
"current_mind": current_mind, "current_mind": current_mind,
"observed_messages": observed_messages, "observed_messages": observed_messages,
"llm_error": llm_error, # 返回错误状态 "llm_error": llm_error, # 返回错误状态
} }
async def _get_anchor_message(self) -> Optional[MessageRecv]: async def _get_anchor_message(self) -> Optional[MessageRecv]:
@@ -1178,8 +1184,8 @@ class HeartFChatting:
action_options_text = "当前你可以选择的行动有:\n" action_options_text = "当前你可以选择的行动有:\n"
action_keys = list(current_available_actions.keys()) action_keys = list(current_available_actions.keys())
for name in action_keys: for name in action_keys:
desc = current_available_actions[name] desc = current_available_actions[name]
action_options_text += f"- '{name}': {desc}\n" action_options_text += f"- '{name}': {desc}\n"
# --- 选择一个示例动作键 (用于填充模板中的 {example_action}) --- # --- 选择一个示例动作键 (用于填充模板中的 {example_action}) ---
example_action_key = action_keys[0] if action_keys else "no_reply" example_action_key = action_keys[0] if action_keys else "no_reply"
@@ -1194,10 +1200,10 @@ class HeartFChatting:
structured_info_block=structured_info_block, structured_info_block=structured_info_block,
chat_content_block=chat_content_block, chat_content_block=chat_content_block,
current_mind_block=current_mind_block, current_mind_block=current_mind_block,
replan="", # 暂时留空 replan 信息 replan="", # 暂时留空 replan 信息
cycle_info_block=cycle_info_block, cycle_info_block=cycle_info_block,
action_options_text=action_options_text, # 传入可用动作描述 action_options_text=action_options_text, # 传入可用动作描述
example_action=example_action_key # 传入示例动作键 example_action=example_action_key, # 传入示例动作键
) )
return prompt return prompt
@@ -1205,7 +1211,7 @@ class HeartFChatting:
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix}[Planner] 构建提示词时出错: {e}") logger.error(f"{self.log_prefix}[Planner] 构建提示词时出错: {e}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return "[构建 Planner Prompt 时出错]" # 返回错误提示,避免空字符串 return "[构建 Planner Prompt 时出错]" # 返回错误提示,避免空字符串
# --- 回复器 (Replier) 的定义 --- # # --- 回复器 (Replier) 的定义 --- #
async def _replier_work( async def _replier_work(

View File

@@ -7,14 +7,13 @@ from src.plugins.utils.chat_message_builder import build_readable_messages, get_
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from src.plugins.chat.utils import get_embedding from src.plugins.chat.utils import get_embedding
import time import time
from typing import Union, Optional, Dict, Any from typing import Union, Optional
from ...common.database import db from ...common.database import db
from ..chat.utils import get_recent_group_speaker from ..chat.utils import get_recent_group_speaker
from ..moods.moods import MoodManager from ..moods.moods import MoodManager
from ..memory_system.Hippocampus import HippocampusManager from ..memory_system.Hippocampus import HippocampusManager
from ..schedule.schedule_generator import bot_schedule from ..schedule.schedule_generator import bot_schedule
from ..knowledge.knowledge_lib import qa_manager from ..knowledge.knowledge_lib import qa_manager
import traceback
logger = get_logger("prompt") logger = get_logger("prompt")
@@ -50,7 +49,7 @@ def init_prompt():
# Planner提示词 - 修改为要求 JSON 输出 # Planner提示词 - 修改为要求 JSON 输出
Prompt( Prompt(
'''你的名字是{bot_name},{prompt_personality},你现在正在一个群聊中。需要基于以下信息决定如何参与对话: """你的名字是{bot_name},{prompt_personality},你现在正在一个群聊中。需要基于以下信息决定如何参与对话:
{structured_info_block} {structured_info_block}
{chat_content_block} {chat_content_block}
{current_mind_block} {current_mind_block}
@@ -106,7 +105,7 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query":
}} }}
请输出你的决策 JSON 请输出你的决策 JSON
''', # 使用三引号避免内部引号问题 """, # 使用三引号避免内部引号问题
"planner_prompt", # 保持名称不变,替换内容 "planner_prompt", # 保持名称不变,替换内容
) )