fix:修复引用回复逻辑

This commit is contained in:
SengokuCola
2025-05-02 02:03:24 +08:00
parent 1ca736cc32
commit 974839c1b5
14 changed files with 194 additions and 155 deletions

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@ NapCat.Framework.Windows.Once/
log/ log/
logs/ logs/
tool_call_benchmark.py tool_call_benchmark.py
run_maibot_core.bat
run_napcat_adapter.bat
run_ad.bat run_ad.bat
llm_tool_benchmark_results.json llm_tool_benchmark_results.json
MaiBot-Napcat-Adapter-main MaiBot-Napcat-Adapter-main

View File

@@ -52,7 +52,7 @@ class SearchKnowledgeFromLPMMTool(BaseTool):
except Exception as e: except Exception as e:
logger.error(f"知识库搜索工具执行失败: {str(e)}") logger.error(f"知识库搜索工具执行失败: {str(e)}")
# 在其他异常情况下,确保 id 仍然是 query (如果它被定义了) # 在其他异常情况下,确保 id 仍然是 query (如果它被定义了)
query_id = query if 'query' in locals() else 'unknown_query' query_id = query if "query" in locals() else "unknown_query"
return {"type": "info", "id": query_id, "content": f"lpmm知识库搜索失败炸了: {str(e)}"} return {"type": "info", "id": query_id, "content": f"lpmm知识库搜索失败炸了: {str(e)}"}
# def get_info_from_db( # def get_info_from_db(
@@ -143,13 +143,15 @@ class SearchKnowledgeFromLPMMTool(BaseTool):
formatted_string = "我找到了一些相关知识:\n" formatted_string = "我找到了一些相关知识:\n"
for i, result in enumerate(results): for i, result in enumerate(results):
chunk_id = result.get("chunk_id") # chunk_id = result.get("chunk_id")
text = result.get("text", "") text = result.get("text", "")
source = result.get("source", "未知来源") source = result.get("source", "未知来源")
source_type = result.get("source_type", "未知类型") source_type = result.get("source_type", "未知类型")
similarity = result.get("similarity", 0.0) similarity = result.get("similarity", 0.0)
formatted_string += f"{i + 1}. (相似度: {similarity:.2f}) 类型: {source_type}, 来源: {source} \n内容片段: {text}\n\n" formatted_string += (
f"{i + 1}. (相似度: {similarity:.2f}) 类型: {source_type}, 来源: {source} \n内容片段: {text}\n\n"
)
# 暂时去掉chunk_id # 暂时去掉chunk_id
# formatted_string += f"{i + 1}. (相似度: {similarity:.2f}) 类型: {source_type}, 来源: {source}, Chunk ID: {chunk_id} \n内容片段: {text}\n\n" # formatted_string += f"{i + 1}. (相似度: {similarity:.2f}) 类型: {source_type}, 来源: {source}, Chunk ID: {chunk_id} \n内容片段: {text}\n\n"

View File

@@ -1,28 +1,24 @@
from src.do_tool.tool_can_use.base_tool import BaseTool, register_tool from src.do_tool.tool_can_use.base_tool import BaseTool, register_tool
from src.plugins.person_info.person_info import person_info_manager from src.plugins.person_info.person_info import person_info_manager
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.plugins.person_info.relationship_manager import relationship_manager
import json
import time import time
logger = get_logger("rename_person_tool") logger = get_logger("rename_person_tool")
class RenamePersonTool(BaseTool): class RenamePersonTool(BaseTool):
name = "rename_person" name = "rename_person"
description = "这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。" description = "这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。"
parameters = { parameters = {
"type": "object", "type": "object",
"properties": { "properties": {
"person_name": { "person_name": {"type": "string", "description": "需要重新取名的用户的当前昵称"},
"type": "string",
"description": "需要重新取名的用户的当前昵称"
},
"message_content": { "message_content": {
"type": "string", "type": "string",
"description": "可选的。当前的聊天内容或特定要求,用于提供取名建议的上下文。" "description": "可选的。当前的聊天内容或特定要求,用于提供取名建议的上下文。",
} },
}, },
"required": ["person_name"] "required": ["person_name"],
} }
async def execute(self, function_args: dict, message_txt=""): async def execute(self, function_args: dict, message_txt=""):
@@ -37,13 +33,10 @@ class RenamePersonTool(BaseTool):
dict: 包含执行结果的字典 dict: 包含执行结果的字典
""" """
person_name_to_find = function_args.get("person_name") person_name_to_find = function_args.get("person_name")
request_context = function_args.get("message_content", "") # 如果没有提供,则为空字符串 request_context = function_args.get("message_content", "") # 如果没有提供,则为空字符串
if not person_name_to_find: if not person_name_to_find:
return { return {"name": self.name, "content": "错误:必须提供需要重命名的用户昵称 (person_name)。"}
"name": self.name,
"content": "错误:必须提供需要重命名的用户昵称 (person_name)。"
}
try: try:
# 1. 根据昵称查找用户信息 # 1. 根据昵称查找用户信息
@@ -54,35 +47,34 @@ class RenamePersonTool(BaseTool):
logger.info(f"未找到昵称为 '{person_name_to_find}' 的用户。") logger.info(f"未找到昵称为 '{person_name_to_find}' 的用户。")
return { return {
"name": self.name, "name": self.name,
"content": f"找不到昵称为 '{person_name_to_find}' 的用户。请确保输入的是我之前为该用户取的昵称。" "content": f"找不到昵称为 '{person_name_to_find}' 的用户。请确保输入的是我之前为该用户取的昵称。",
} }
person_id = person_info.get("person_id") person_id = person_info.get("person_id")
user_nickname = person_info.get("nickname") # 这是用户原始昵称 user_nickname = person_info.get("nickname") # 这是用户原始昵称
user_cardname = person_info.get("user_cardname") user_cardname = person_info.get("user_cardname")
user_avatar = person_info.get("user_avatar") user_avatar = person_info.get("user_avatar")
if not person_id: if not person_id:
logger.error(f"找到了用户 '{person_name_to_find}' 但无法获取 person_id") logger.error(f"找到了用户 '{person_name_to_find}' 但无法获取 person_id")
return { return {"name": self.name, "content": f"找到了用户 '{person_name_to_find}' 但获取内部ID时出错。"}
"name": self.name,
"content": f"找到了用户 '{person_name_to_find}' 但获取内部ID时出错。"
}
# 2. 调用 qv_person_name 进行取名 # 2. 调用 qv_person_name 进行取名
logger.debug(f"为用户 {person_id} (原昵称: {person_name_to_find}) 调用 qv_person_name请求上下文: '{request_context}'") logger.debug(
f"为用户 {person_id} (原昵称: {person_name_to_find}) 调用 qv_person_name请求上下文: '{request_context}'"
)
result = await person_info_manager.qv_person_name( result = await person_info_manager.qv_person_name(
person_id=person_id, person_id=person_id,
user_nickname=user_nickname, user_nickname=user_nickname,
user_cardname=user_cardname, user_cardname=user_cardname,
user_avatar=user_avatar, user_avatar=user_avatar,
request=request_context request=request_context,
) )
# 3. 处理结果 # 3. 处理结果
if result and result.get("nickname"): if result and result.get("nickname"):
new_name = result["nickname"] new_name = result["nickname"]
reason = result.get("reason", "未提供理由") # reason = result.get("reason", "未提供理由")
logger.info(f"成功为用户 {person_id} 取了新昵称: {new_name}") logger.info(f"成功为用户 {person_id} 取了新昵称: {new_name}")
content = f"已成功将用户 {person_name_to_find} 的备注名更新为 {new_name}" content = f"已成功将用户 {person_name_to_find} 的备注名更新为 {new_name}"
@@ -93,14 +85,14 @@ class RenamePersonTool(BaseTool):
# 尝试从内存中获取可能已经更新的名字 # 尝试从内存中获取可能已经更新的名字
current_name = await person_info_manager.get_value(person_id, "person_name") current_name = await person_info_manager.get_value(person_id, "person_name")
if current_name and current_name != person_name_to_find: if current_name and current_name != person_name_to_find:
return { return {
"name": self.name, "name": self.name,
"content": f"尝试取新昵称时遇到一点小问题,但我已经将 '{person_name_to_find}' 的昵称更新为 '{current_name}' 了。" "content": f"尝试取新昵称时遇到一点小问题,但我已经将 '{person_name_to_find}' 的昵称更新为 '{current_name}' 了。",
} }
else: else:
return { return {
"name": self.name, "name": self.name,
"content": f"尝试为 '{person_name_to_find}' 取新昵称时遇到了问题,未能成功生成。可能需要稍后再试。" "content": f"尝试为 '{person_name_to_find}' 取新昵称时遇到了问题,未能成功生成。可能需要稍后再试。",
} }
except Exception as e: except Exception as e:
@@ -108,5 +100,6 @@ class RenamePersonTool(BaseTool):
logger.error(error_msg, exc_info=True) logger.error(error_msg, exc_info=True)
return {"type": "info_error", "id": f"rename_error_{time.time()}", "content": error_msg} return {"type": "info_error", "id": f"rename_error_{time.time()}", "content": error_msg}
# 注册工具 # 注册工具
register_tool(RenamePersonTool) register_tool(RenamePersonTool)

View File

@@ -18,7 +18,7 @@ INTEREST_EVAL_INTERVAL_SECONDS = 5
# 新增聊天超时检查间隔 # 新增聊天超时检查间隔
NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60 NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60
# 新增状态评估间隔 # 新增状态评估间隔
HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS = 60 HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS = 20
# 新增私聊激活检查间隔 # 新增私聊激活检查间隔
PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS = 5 # 与兴趣评估类似设为5秒 PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS = 5 # 与兴趣评估类似设为5秒

View File

@@ -14,6 +14,8 @@ from src.plugins.utils.chat_message_builder import (
) )
from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager
from typing import Optional from typing import Optional
import difflib
from src.plugins.chat.message import MessageRecv # 添加 MessageRecv 导入
# Import the new utility function # Import the new utility function
from .utils_chat import get_chat_type_and_target_info from .utils_chat import get_chat_type_and_target_info
@@ -227,6 +229,70 @@ class ChattingObservation(Observation):
f"Chat {self.chat_id} - 压缩早期记忆:{self.mid_memory_info}\n现在聊天内容:{self.talking_message_str}" f"Chat {self.chat_id} - 压缩早期记忆:{self.mid_memory_info}\n现在聊天内容:{self.talking_message_str}"
) )
async def find_best_matching_message(self, search_str: str, min_similarity: float = 0.6) -> Optional[MessageRecv]:
"""
在 talking_message 中查找与 search_str 最匹配的消息。
Args:
search_str: 要搜索的字符串。
min_similarity: 要求的最低相似度0到1之间
Returns:
匹配的 MessageRecv 实例,如果找不到则返回 None。
"""
best_match_score = -1.0
best_match_dict = None
if not self.talking_message:
logger.debug(f"Chat {self.chat_id}: talking_message is empty, cannot find match for '{search_str}'")
return None
for message_dict in self.talking_message:
try:
# 临时创建 MessageRecv 以处理文本
temp_msg = MessageRecv(message_dict)
await temp_msg.process() # 处理消息以获取 processed_plain_text
current_text = temp_msg.processed_plain_text
if not current_text: # 跳过没有文本内容的消息
continue
# 计算相似度
matcher = difflib.SequenceMatcher(None, search_str, current_text)
score = matcher.ratio()
# logger.debug(f"Comparing '{search_str}' with '{current_text}', score: {score}") # 可选:用于调试
if score > best_match_score:
best_match_score = score
best_match_dict = message_dict
except Exception as e:
logger.error(f"Error processing message for matching in chat {self.chat_id}: {e}", exc_info=True)
continue # 继续处理下一条消息
if best_match_dict is not None and best_match_score >= min_similarity:
logger.debug(f"Found best match for '{search_str}' with score {best_match_score:.2f}")
try:
final_msg = MessageRecv(best_match_dict)
await final_msg.process()
# 确保 MessageRecv 实例有关联的 chat_stream
if hasattr(self, "chat_stream"):
final_msg.update_chat_stream(self.chat_stream)
else:
logger.warning(
f"ChattingObservation instance for chat {self.chat_id} does not have a chat_stream attribute set."
)
return final_msg
except Exception as e:
logger.error(f"Error creating final MessageRecv for chat {self.chat_id}: {e}", exc_info=True)
return None
else:
logger.debug(
f"No suitable match found for '{search_str}' in chat {self.chat_id} (best score: {best_match_score:.2f}, threshold: {min_similarity})"
)
return None
async def has_new_messages_since(self, timestamp: float) -> bool: async def has_new_messages_since(self, timestamp: float) -> bool:
"""检查指定时间戳之后是否有新消息""" """检查指定时间戳之后是否有新消息"""
count = num_new_messages_since(chat_id=self.chat_id, timestamp_start=timestamp) count = num_new_messages_since(chat_id=self.chat_id, timestamp_start=timestamp)

View File

@@ -1,4 +1,4 @@
from .observation import Observation, ChattingObservation from .observation import ChattingObservation
from src.plugins.models.utils_model import LLMRequest from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
import time import time
@@ -146,10 +146,9 @@ class SubMind:
lines = ["【信息】"] lines = ["【信息】"]
for item in self.structured_info: for item in self.structured_info:
# 简化展示突出内容和类型包含TTL供调试 # 简化展示突出内容和类型包含TTL供调试
type_str = item.get('type', '未知类型') type_str = item.get("type", "未知类型")
content_str = item.get('content', '') content_str = item.get("content", "")
ttl = item.get('ttl', '?')
if type_str == "info": if type_str == "info":
lines.append(f"刚刚: {content_str}") lines.append(f"刚刚: {content_str}")
elif type_str == "memory": elif type_str == "memory":
@@ -178,18 +177,24 @@ class SubMind:
# ---------- 0. 更新和清理 structured_info ---------- # ---------- 0. 更新和清理 structured_info ----------
if self.structured_info: if self.structured_info:
logger.debug(f"{self.log_prefix} 更新前的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}") logger.debug(
f"{self.log_prefix} 更新前的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}"
)
updated_info = [] updated_info = []
for item in self.structured_info: for item in self.structured_info:
item['ttl'] -= 1 item["ttl"] -= 1
if item['ttl'] > 0: if item["ttl"] > 0:
updated_info.append(item) updated_info.append(item)
else: else:
logger.debug(f"{self.log_prefix} 移除过期的 structured_info 项: {item['id']}") logger.debug(f"{self.log_prefix} 移除过期的 structured_info 项: {item['id']}")
self.structured_info = updated_info self.structured_info = updated_info
logger.debug(f"{self.log_prefix} 更新后的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}") logger.debug(
f"{self.log_prefix} 更新后的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}"
)
self._update_structured_info_str() self._update_structured_info_str()
logger.debug(f"{self.log_prefix} 当前完整的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}") logger.debug(
f"{self.log_prefix} 当前完整的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}"
)
# ---------- 1. 准备基础数据 ---------- # ---------- 1. 准备基础数据 ----------
# 获取现有想法和情绪状态 # 获取现有想法和情绪状态
@@ -202,10 +207,10 @@ class SubMind:
logger.error(f"{self.log_prefix} 无法获取有效的观察对象或缺少聊天类型信息") logger.error(f"{self.log_prefix} 无法获取有效的观察对象或缺少聊天类型信息")
self.update_current_mind("(观察出错了...)") self.update_current_mind("(观察出错了...)")
return self.current_mind, self.past_mind return self.current_mind, self.past_mind
is_group_chat = observation.is_group_chat is_group_chat = observation.is_group_chat
# logger.debug(f"is_group_chat: {is_group_chat}") # logger.debug(f"is_group_chat: {is_group_chat}")
chat_target_info = observation.chat_target_info chat_target_info = observation.chat_target_info
chat_target_name = "对方" # Default for private chat_target_name = "对方" # Default for private
if not is_group_chat and chat_target_info: if not is_group_chat and chat_target_info:
@@ -496,7 +501,7 @@ class SubMind:
tool_instance: 工具使用器实例 tool_instance: 工具使用器实例
""" """
tool_results = [] tool_results = []
new_structured_items = [] # 收集新产生的结构化信息 new_structured_items = [] # 收集新产生的结构化信息
# 执行所有工具调用 # 执行所有工具调用
for tool_call in tool_calls: for tool_call in tool_calls:
@@ -506,26 +511,26 @@ class SubMind:
tool_results.append(result) tool_results.append(result)
# 创建新的结构化信息项 # 创建新的结构化信息项
new_item = { new_item = {
"type": result.get("type", "unknown_type"), # 使用 'type' 键 "type": result.get("type", "unknown_type"), # 使用 'type' 键
"id": result.get("id", f"fallback_id_{time.time()}"), # 使用 'id' 键 "id": result.get("id", f"fallback_id_{time.time()}"), # 使用 'id' 键
"content": result.get("content", ""), # 'content' 键保持不变 "content": result.get("content", ""), # 'content' 键保持不变
"ttl": 3 "ttl": 3,
} }
new_structured_items.append(new_item) new_structured_items.append(new_item)
except Exception as tool_e: except Exception as tool_e:
logger.error(f"[{self.subheartflow_id}] 工具执行失败: {tool_e}") logger.error(f"[{self.subheartflow_id}] 工具执行失败: {tool_e}")
logger.error(traceback.format_exc()) # 添加 traceback 记录 logger.error(traceback.format_exc()) # 添加 traceback 记录
# 如果有新的工具结果,记录并更新结构化信息 # 如果有新的工具结果,记录并更新结构化信息
if new_structured_items: if new_structured_items:
self.structured_info.extend(new_structured_items) # 添加到现有列表 self.structured_info.extend(new_structured_items) # 添加到现有列表
logger.debug(f"工具调用收集到新的结构化信息: {safe_json_dumps(new_structured_items, ensure_ascii=False)}") logger.debug(f"工具调用收集到新的结构化信息: {safe_json_dumps(new_structured_items, ensure_ascii=False)}")
# logger.debug(f"当前完整的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}") # 可以取消注释以查看完整列表 # logger.debug(f"当前完整的 structured_info: {safe_json_dumps(self.structured_info, ensure_ascii=False)}") # 可以取消注释以查看完整列表
self._update_structured_info_str() # 添加新信息后,更新字符串表示 self._update_structured_info_str() # 添加新信息后,更新字符串表示
def update_current_mind(self, response): def update_current_mind(self, response):
if self.current_mind: # 只有当 current_mind 非空时才添加到 past_mind if self.current_mind: # 只有当 current_mind 非空时才添加到 past_mind
self.past_mind.append(self.current_mind) self.past_mind.append(self.current_mind)
# 可以考虑限制 past_mind 的大小,例如: # 可以考虑限制 past_mind 的大小,例如:
# max_past_mind_size = 10 # max_past_mind_size = 10

View File

@@ -128,7 +128,7 @@ class SubHeartflowManager:
# 添加聊天观察者 # 添加聊天观察者
observation = ChattingObservation(chat_id=subheartflow_id) observation = ChattingObservation(chat_id=subheartflow_id)
await observation.initialize() await observation.initialize()
new_subflow.add_observation(observation) new_subflow.add_observation(observation)
# 注册子心流 # 注册子心流

View File

@@ -82,15 +82,14 @@ class ChatBot:
if userinfo.user_id in global_config.ban_user_id: if userinfo.user_id in global_config.ban_user_id:
logger.debug(f"用户{userinfo.user_id}被禁止回复") logger.debug(f"用户{userinfo.user_id}被禁止回复")
return return
if groupinfo is None: if groupinfo is None:
logger.trace("检测到私聊消息,检查") logger.trace("检测到私聊消息,检查")
# 好友黑名单拦截 # 好友黑名单拦截
if userinfo.user_id not in global_config.talk_allowed_private: if userinfo.user_id not in global_config.talk_allowed_private:
logger.debug(f"用户{userinfo.user_id}没有私聊权限") logger.debug(f"用户{userinfo.user_id}没有私聊权限")
return return
# 群聊黑名单拦截 # 群聊黑名单拦截
if groupinfo is not None and groupinfo.group_id not in global_config.talk_allowed_groups: if groupinfo is not None and groupinfo.group_id not in global_config.talk_allowed_groups:
logger.trace(f"{groupinfo.group_id}被禁止回复") logger.trace(f"{groupinfo.group_id}被禁止回复")

View File

@@ -294,7 +294,9 @@ class MessageSending(MessageProcessBase):
def set_reply(self, reply: Optional["MessageRecv"] = None): def set_reply(self, reply: Optional["MessageRecv"] = None):
"""设置回复消息""" """设置回复消息"""
if self.message_info.format_info is not None and "reply" in self.message_info.format_info.accept_format: # print(f"set_reply: {reply}")
# if self.message_info.format_info is not None and "reply" in self.message_info.format_info.accept_format:
if True:
if reply: if reply:
self.reply = reply self.reply = reply
if self.reply: if self.reply:

View File

@@ -209,24 +209,31 @@ class MessageManager:
_ = message.update_thinking_time() # 更新思考时间 _ = message.update_thinking_time() # 更新思考时间
thinking_start_time = message.thinking_start_time thinking_start_time = message.thinking_start_time
now_time = time.time() now_time = time.time()
logger.debug(f"thinking_start_time:{thinking_start_time},now_time:{now_time}")
thinking_messages_count, thinking_messages_length = count_messages_between( thinking_messages_count, thinking_messages_length = count_messages_between(
start_time=thinking_start_time, end_time=now_time, stream_id=message.chat_stream.stream_id start_time=thinking_start_time, end_time=now_time, stream_id=message.chat_stream.stream_id
) )
# print(f"message.reply:{message.reply}")
# --- 条件应用 set_reply 逻辑 --- # --- 条件应用 set_reply 逻辑 ---
logger.debug(
f"[message.apply_set_reply_logic:{message.apply_set_reply_logic},message.is_head:{message.is_head},thinking_messages_count:{thinking_messages_count},thinking_messages_length:{thinking_messages_length},message.is_private_message():{message.is_private_message()}]"
)
if ( if (
message.apply_set_reply_logic # 检查标记 message.apply_set_reply_logic # 检查标记
and message.is_head and message.is_head
and (thinking_messages_count > 4 or thinking_messages_length > 250) and (thinking_messages_count > 1 or thinking_messages_length > 20)
and not message.is_private_message() and not message.is_private_message()
): ):
logger.debug( logger.debug(
f"[{message.chat_stream.stream_id}] 应用 set_reply 逻辑: {message.processed_plain_text[:20]}..." f"[{message.chat_stream.stream_id}] 应用 set_reply 逻辑: {message.processed_plain_text[:20]}..."
) )
message.set_reply() message.set_reply(message.reply)
# --- 结束条件 set_reply --- # --- 结束条件 set_reply ---
await message.process() # 预处理消息内容 await message.process() # 预处理消息内容
logger.debug(f"{message}")
# 使用全局 message_sender 实例 # 使用全局 message_sender 实例
await send_message(message) await send_message(message)

View File

@@ -6,13 +6,13 @@ from collections import Counter
import jieba import jieba
import numpy as np import numpy as np
from src.common.logger import get_module_logger from src.common.logger import get_module_logger
from pymongo.errors import PyMongoError
from ..models.utils_model import LLMRequest from ..models.utils_model import LLMRequest
from ..utils.typo_generator import ChineseTypoGenerator from ..utils.typo_generator import ChineseTypoGenerator
from ...config.config import global_config from ...config.config import global_config
from .message import MessageRecv, Message from .message import MessageRecv
from maim_message import UserInfo from maim_message import UserInfo
from .chat_stream import ChatStream
from ..moods.moods import MoodManager from ..moods.moods import MoodManager
from ...common.database import db from ...common.database import db
@@ -107,8 +107,6 @@ async def get_embedding(text, request_type="embedding"):
return embedding return embedding
def get_recent_group_detailed_plain_text(chat_stream_id: str, limit: int = 12, combine=False): def get_recent_group_detailed_plain_text(chat_stream_id: str, limit: int = 12, combine=False):
recent_messages = list( recent_messages = list(
db.messages.find( db.messages.find(
@@ -566,93 +564,45 @@ def count_messages_between(start_time: float, end_time: float, stream_id: str) -
"""计算两个时间点之间的消息数量和文本总长度 """计算两个时间点之间的消息数量和文本总长度
Args: Args:
start_time (float): 起始时间戳 start_time (float): 起始时间戳 (不包含)
end_time (float): 结束时间戳 end_time (float): 结束时间戳 (包含)
stream_id (str): 聊天流ID stream_id (str): 聊天流ID
Returns: Returns:
tuple[int, int]: (消息数量, 文本总长度) tuple[int, int]: (消息数量, 文本总长度)
- 消息数量:包含起始时间的消息,不包含结束时间的消息
- 文本总长度所有消息的processed_plain_text长度之和
""" """
count = 0
total_length = 0
# 参数校验 (可选但推荐)
if start_time >= end_time:
# logger.debug(f"开始时间 {start_time} 大于或等于结束时间 {end_time},返回 0, 0")
return 0, 0
if not stream_id:
logger.error("stream_id 不能为空")
return 0, 0
# 直接查询时间范围内的消息
# time > start_time AND time <= end_time
query = {"chat_id": stream_id, "time": {"$gt": start_time, "$lte": end_time}}
try: try:
# 获取开始时间之前最新的一条消息 # 执行查询
start_message = db.messages.find_one( messages_cursor = db.messages.find(query)
{"chat_id": stream_id, "time": {"$lte": start_time}},
sort=[("time", -1), ("_id", -1)], # 按时间倒序_id倒序最后插入的在前
)
# 获取结束时间最近的一条消息 # 遍历结果计算数量和长度
# 先找到结束时间点的所有消息 for msg in messages_cursor:
end_time_messages = list(
db.messages.find(
{"chat_id": stream_id, "time": {"$lte": end_time}},
sort=[("time", -1)], # 先按时间倒序
).limit(10)
) # 限制查询数量,避免性能问题
if not end_time_messages:
logger.warning(f"未找到结束时间 {end_time} 之前的消息")
return 0, 0
# 找到最大时间
max_time = end_time_messages[0]["time"]
# 在最大时间的消息中找最后插入的_id最大的
end_message = max([msg for msg in end_time_messages if msg["time"] == max_time], key=lambda x: x["_id"])
if not start_message:
logger.warning(f"未找到开始时间 {start_time} 之前的消息")
return 0, 0
# 调试输出
# print("\n=== 消息范围信息 ===")
# print("Start message:", {
# "message_id": start_message.get("message_id"),
# "time": start_message.get("time"),
# "text": start_message.get("processed_plain_text", ""),
# "_id": str(start_message.get("_id"))
# })
# print("End message:", {
# "message_id": end_message.get("message_id"),
# "time": end_message.get("time"),
# "text": end_message.get("processed_plain_text", ""),
# "_id": str(end_message.get("_id"))
# })
# print("Stream ID:", stream_id)
# 如果结束消息的时间等于开始时间返回0
if end_message["time"] == start_message["time"]:
return 0, 0
# 获取并打印这个时间范围内的所有消息
# print("\n=== 时间范围内的所有消息 ===")
all_messages = list(
db.messages.find(
{"chat_id": stream_id, "time": {"$gte": start_message["time"], "$lte": end_message["time"]}},
sort=[("time", 1), ("_id", 1)], # 按时间正序_id正序
)
)
count = 0
total_length = 0
for msg in all_messages:
count += 1 count += 1
text_length = len(msg.get("processed_plain_text", "")) total_length += len(msg.get("processed_plain_text", ""))
total_length += text_length
# print(f"\n消息 {count}:")
# print({
# "message_id": msg.get("message_id"),
# "time": msg.get("time"),
# "text": msg.get("processed_plain_text", ""),
# "text_length": text_length,
# "_id": str(msg.get("_id"))
# })
# 如果时间不同需要把end_message本身也计入 # logger.debug(f"查询范围 ({start_time}, {end_time}] 内找到 {count} 条消息,总长度 {total_length}")
return count - 1, total_length return count, total_length
except Exception as e: except PyMongoError as e:
logger.error(f"计算消息数量时出错: {str(e)}") logger.error(f"查询 stream_id={stream_id} 在 ({start_time}, {end_time}] 范围内的消息时出错: {e}")
return 0, 0
except Exception as e: # 保留一个通用异常捕获以防万一
logger.error(f"计算消息数量时发生意外错误: {e}")
return 0, 0 return 0, 0

View File

@@ -372,6 +372,7 @@ class PromptBuilder:
memory_prompt = await global_prompt_manager.format_prompt( memory_prompt = await global_prompt_manager.format_prompt(
"memory_prompt", related_memory_info=related_memory_info "memory_prompt", related_memory_info=related_memory_info
) )
message_list_before_now = get_raw_msg_before_timestamp_with_chat( message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id, chat_id=chat_stream.stream_id,
timestamp=time.time(), timestamp=time.time(),

View File

@@ -86,11 +86,12 @@ class NormalChat:
bot_user_info=bot_user_info, bot_user_info=bot_user_info,
reply=message, reply=message,
thinking_start_time=thinking_time_point, thinking_start_time=thinking_time_point,
timestamp=timestamp if timestamp is not None else None timestamp=timestamp if timestamp is not None else None,
) )
await message_manager.add_message(thinking_message) await message_manager.add_message(thinking_message)
return thinking_id return thinking_id
# 改为实例方法 # 改为实例方法
async def _add_messages_to_manager( async def _add_messages_to_manager(
self, message: MessageRecv, response_set: List[str], thinking_id self, message: MessageRecv, response_set: List[str], thinking_id
@@ -206,7 +207,10 @@ class NormalChat:
try: try:
# 处理消息 # 处理消息
await self.normal_response( await self.normal_response(
message=message, is_mentioned=is_mentioned, interested_rate=interest_value, rewind_response = False message=message,
is_mentioned=is_mentioned,
interested_rate=interest_value,
rewind_response=False,
) )
except Exception as e: except Exception as e:
logger.error(f"[{self.stream_name}] 处理兴趣消息{msg_id}时出错: {e}\n{traceback.format_exc()}") logger.error(f"[{self.stream_name}] 处理兴趣消息{msg_id}时出错: {e}\n{traceback.format_exc()}")
@@ -214,7 +218,9 @@ class NormalChat:
self.interest_dict.pop(msg_id, None) self.interest_dict.pop(msg_id, None)
# 改为实例方法, 移除 chat 参数 # 改为实例方法, 移除 chat 参数
async def normal_response(self, message: MessageRecv, is_mentioned: bool, interested_rate: float, rewind_response: bool = False) -> None: async def normal_response(
self, message: MessageRecv, is_mentioned: bool, interested_rate: float, rewind_response: bool = False
) -> None:
# 检查收到的消息是否属于当前实例处理的 chat stream # 检查收到的消息是否属于当前实例处理的 chat stream
if message.chat_stream.stream_id != self.stream_id: if message.chat_stream.stream_id != self.stream_id:
logger.error( logger.error(
@@ -393,13 +399,17 @@ class NormalChat:
try: try:
logger.info(f"[{self.stream_name}] 处理初始高兴趣消息 {msg_id} (兴趣值: {interest_value:.2f})") logger.info(f"[{self.stream_name}] 处理初始高兴趣消息 {msg_id} (兴趣值: {interest_value:.2f})")
await self.normal_response(message=message, is_mentioned=is_mentioned, interested_rate=interest_value, rewind_response = True) await self.normal_response(
message=message, is_mentioned=is_mentioned, interested_rate=interest_value, rewind_response=True
)
processed_count += 1 processed_count += 1
except Exception as e: except Exception as e:
logger.error(f"[{self.stream_name}] 处理初始兴趣消息 {msg_id} 时出错: {e}\\n{traceback.format_exc()}") logger.error(f"[{self.stream_name}] 处理初始兴趣消息 {msg_id} 时出错: {e}\\n{traceback.format_exc()}")
# --- 新增:处理完后清空整个字典 --- # --- 新增:处理完后清空整个字典 ---
logger.info(f"[{self.stream_name}] 处理了 {processed_count} 条初始高兴趣消息。现在清空所有剩余的初始兴趣消息...") logger.info(
f"[{self.stream_name}] 处理了 {processed_count} 条初始高兴趣消息。现在清空所有剩余的初始兴趣消息..."
)
self.interest_dict.clear() self.interest_dict.clear()
# --- 新增结束 --- # --- 新增结束 ---

View File

@@ -188,7 +188,9 @@ class PersonInfoManager:
logger.warning(f"无法从文本中提取有效的JSON字典: {text}") logger.warning(f"无法从文本中提取有效的JSON字典: {text}")
return {"nickname": "", "reason": ""} return {"nickname": "", "reason": ""}
async def qv_person_name(self, person_id: str, user_nickname: str, user_cardname: str, user_avatar: str, request: str = ""): async def qv_person_name(
self, person_id: str, user_nickname: str, user_cardname: str, user_avatar: str, request: str = ""
):
"""给某个用户取名""" """给某个用户取名"""
if not person_id: if not person_id:
logger.debug("取名失败person_id不能为空") logger.debug("取名失败person_id不能为空")
@@ -214,7 +216,7 @@ class PersonInfoManager:
qv_name_prompt += f"你之前叫他{old_name},是因为{old_reason}" qv_name_prompt += f"你之前叫他{old_name},是因为{old_reason}"
qv_name_prompt += f"\n其他取名的要求是:{request}" qv_name_prompt += f"\n其他取名的要求是:{request}"
qv_name_prompt += "\n请根据以上用户信息想想你叫他什么比较好请最好使用用户的qq昵称可以稍作修改" qv_name_prompt += "\n请根据以上用户信息想想你叫他什么比较好请最好使用用户的qq昵称可以稍作修改"
if existing_names: if existing_names:
qv_name_prompt += f"\n请注意,以下名称已被使用,不要使用以下昵称:{existing_names}\n" qv_name_prompt += f"\n请注意,以下名称已被使用,不要使用以下昵称:{existing_names}\n"
@@ -526,7 +528,7 @@ class PersonInfoManager:
for pid, name in self.person_name_list.items(): for pid, name in self.person_name_list.items():
if name == person_name: if name == person_name:
found_person_id = pid found_person_id = pid
break # 找到第一个匹配就停止 break # 找到第一个匹配就停止
if not found_person_id: if not found_person_id:
# 如果内存没有,尝试数据库查询(可能内存未及时更新或启动时未加载) # 如果内存没有,尝试数据库查询(可能内存未及时更新或启动时未加载)
@@ -535,21 +537,21 @@ class PersonInfoManager:
found_person_id = document.get("person_id") found_person_id = document.get("person_id")
else: else:
logger.debug(f"数据库中也未找到名为 '{person_name}' 的用户") logger.debug(f"数据库中也未找到名为 '{person_name}' 的用户")
return None # 数据库也找不到 return None # 数据库也找不到
# 根据找到的 person_id 获取所需信息 # 根据找到的 person_id 获取所需信息
if found_person_id: if found_person_id:
required_fields = ["person_id", "platform", "user_id", "nickname", "user_cardname", "user_avatar"] required_fields = ["person_id", "platform", "user_id", "nickname", "user_cardname", "user_avatar"]
person_data = await self.get_values(found_person_id, required_fields) person_data = await self.get_values(found_person_id, required_fields)
if person_data: # 确保 get_values 成功返回 if person_data: # 确保 get_values 成功返回
return person_data return person_data
else: else:
logger.warning(f"找到了 person_id '{found_person_id}' 但获取详细信息失败") logger.warning(f"找到了 person_id '{found_person_id}' 但获取详细信息失败")
return None return None
else: else:
# 这理论上不应该发生,因为上面已经处理了找不到的情况 # 这理论上不应该发生,因为上面已经处理了找不到的情况
logger.error(f"逻辑错误:未能为 '{person_name}' 确定 person_id") logger.error(f"逻辑错误:未能为 '{person_name}' 确定 person_id")
return None return None
person_info_manager = PersonInfoManager() person_info_manager = PersonInfoManager()