diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 95e89aa72..f4502ee0f 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -2,7 +2,7 @@ import asyncio import time import traceback from random import random -from typing import List, Optional # 导入 Optional +from typing import List, Optional, Dict, Any # 导入类型提示 from maim_message import UserInfo, Seg from src.common.logger import get_logger from src.chat.heart_flow.utils_chat import get_chat_type_and_target_info @@ -811,10 +811,10 @@ class NormalChat: # 检查是否满足关系构建条件 should_build_relation = ( - total_messages >= 40 # 40条消息必定满足 - or (total_messages >= 25 and time_elapsed >= 600) # 25条且10分钟 - or (total_messages >= 20 and time_elapsed >= 900) # 20条且30分钟 - or (total_messages >= 10 and time_elapsed >= 1800) # 10条且1小时 + total_messages >= 30 # 30条消息必定满足 + or (total_messages >= 15 and time_elapsed >= 600) # 15条且10分钟 + or (total_messages >= 10 and time_elapsed >= 900) # 10条且15分钟 + or (total_messages >= 5 and time_elapsed >= 1800) # 5条且30 ) if should_build_relation: @@ -894,21 +894,73 @@ class NormalChat: end_time = stats["last_time"] # 获取该时间段的所有消息用于关系构建 - messages = get_raw_msg_by_timestamp_with_chat(self.stream_id, start_time, end_time) + main_messages = get_raw_msg_by_timestamp_with_chat(self.stream_id, start_time, end_time) - if messages: - logger.info(f"[{self.stream_name}] 为用户 {person_id} 获取到 {len(messages)} 条消息用于关系构建") - - # 调用关系管理器更新印象 - relationship_manager = get_relationship_manager() - await relationship_manager.update_person_impression( - person_id=person_id, timestamp=end_time, bot_engaged_messages=messages - ) - - logger.info(f"[{self.stream_name}] 用户 {person_id} 关系构建完成") - else: + if not main_messages: logger.warning(f"[{self.stream_name}] 未找到用户 {person_id} 的消息,关系构建跳过") + return + + # 获取第一条消息的时间戳,然后获取之前的5条消息 + first_message_time = main_messages[0]["time"] + before_messages = self._get_messages_before_timestamp(first_message_time, 5) + + # 获取最后一条消息的时间戳,然后获取之后的5条消息 + last_message_time = main_messages[-1]["time"] + after_messages = self._get_messages_after_timestamp(last_message_time, 5) + + # 合并所有消息并去重 + all_messages = before_messages + main_messages + after_messages + + # 根据消息ID去重并按时间排序 + seen_ids = set() + unique_messages = [] + for msg in all_messages: + msg_id = msg["message_id"] + if msg_id not in seen_ids: + seen_ids.add(msg_id) + unique_messages.append(msg) + + # 按时间排序 + unique_messages.sort(key=lambda x: x["time"]) + + logger.info( + f"[{self.stream_name}] 为用户 {person_id} 获取到消息用于关系构建: " + f"原时间段内 {len(main_messages)} 条,之前 {len(before_messages)} 条," + f"之后 {len(after_messages)} 条,去重后总计 {len(unique_messages)} 条" + ) + + # 调用关系管理器更新印象 + relationship_manager = get_relationship_manager() + await relationship_manager.update_person_impression( + person_id=person_id, timestamp=end_time, bot_engaged_messages=unique_messages + ) + + logger.info(f"[{self.stream_name}] 用户 {person_id} 关系构建完成") except Exception as e: logger.error(f"[{self.stream_name}] 为用户 {person_id} 构建关系时出错: {e}") traceback.print_exc() + + def _get_messages_before_timestamp(self, timestamp: float, limit: int = 5) -> List[Dict[str, Any]]: + """获取指定时间戳之前的指定数量消息""" + try: + from src.common.message_repository import find_messages + filter_query = {"chat_id": self.stream_id, "time": {"$lt": timestamp}} + sort_order = [("time", -1)] # 倒序排列,取最近的几条 + messages = find_messages(message_filter=filter_query, sort=sort_order, limit=limit) + # 返回时保持正序 + return sorted(messages, key=lambda x: x["time"]) + except Exception as e: + logger.error(f"[{self.stream_name}] 获取时间戳之前的消息失败: {e}") + return [] + + def _get_messages_after_timestamp(self, timestamp: float, limit: int = 5) -> List[Dict[str, Any]]: + """获取指定时间戳之后的指定数量消息""" + try: + from src.common.message_repository import find_messages + filter_query = {"chat_id": self.stream_id, "time": {"$gt": timestamp}} + sort_order = [("time", 1)] # 正序排列,取最早的几条 + return find_messages(message_filter=filter_query, sort=sort_order, limit=limit) + except Exception as e: + logger.error(f"[{self.stream_name}] 获取时间戳之后的消息失败: {e}") + return [] diff --git a/src/chat/normal_chat/willing/mode_mxp.py b/src/chat/normal_chat/willing/mode_mxp.py index 6f5cefa97..2a294d0c5 100644 --- a/src/chat/normal_chat/willing/mode_mxp.py +++ b/src/chat/normal_chat/willing/mode_mxp.py @@ -5,7 +5,7 @@ Mxp 模式:梦溪畔独家赞助 此模式的可变参数暂时比较草率,需要调参仙人的大手 此模式的特点: 1.每个聊天流的每个用户的意愿是独立的 -2.接入关系系统,关系会影响意愿值 +2.接入关系系统,关系会影响意愿值(已移除,因为关系系统重构) 3.会根据群聊的热度来调整基础意愿值 4.限制同时思考的消息数量,防止喷射 5.拥有单聊增益,无论在群里还是私聊,只要bot一直和你聊,就会增加意愿值 @@ -83,9 +83,10 @@ class MxpWillingManager(BaseWillingManager): """回复后处理""" async with self.lock: w_info = self.ongoing_messages[message_id] - rel_value = await w_info.person_info_manager.get_value(w_info.person_id, "relationship_value") - rel_level = self._get_relationship_level_num(rel_value) - self.chat_person_reply_willing[w_info.chat_id][w_info.person_id] += rel_level * 0.05 + # 移除关系值相关代码 + # rel_value = await w_info.person_info_manager.get_value(w_info.person_id, "relationship_value") + # rel_level = self._get_relationship_level_num(rel_value) + # self.chat_person_reply_willing[w_info.chat_id][w_info.person_id] += rel_level * 0.05 now_chat_new_person = self.last_response_person.get(w_info.chat_id, [w_info.person_id, 0]) if now_chat_new_person[0] == w_info.person_id: @@ -135,12 +136,7 @@ class MxpWillingManager(BaseWillingManager): self.chat_person_reply_willing[w_info.chat_id][w_info.person_id] = current_willing - rel_value = await w_info.person_info_manager.get_value(w_info.person_id, "relationship_value") - rel_level = self._get_relationship_level_num(rel_value) - current_willing += rel_level * 0.1 - if self.is_debug and rel_level != 0: - self.logger.debug(f"关系增益:{rel_level * 0.1}") - + # 添加单聊增益 if ( w_info.chat_id in self.last_response_person and self.last_response_person[w_info.chat_id][0] == w_info.person_id @@ -288,25 +284,6 @@ class MxpWillingManager(BaseWillingManager): if self.is_debug: self.logger.debug(f"聊天流意愿值更新:{self.chat_reply_willing}") - @staticmethod - def _get_relationship_level_num(relationship_value) -> int: - """关系等级计算""" - if -1000 <= relationship_value < -227: - level_num = 0 - elif -227 <= relationship_value < -73: - level_num = 1 - elif -73 <= relationship_value < 227: - level_num = 2 - elif 227 <= relationship_value < 587: - level_num = 3 - elif 587 <= relationship_value < 900: - level_num = 4 - elif 900 <= relationship_value <= 1000: - level_num = 5 - else: - level_num = 5 if relationship_value > 1000 else 0 - return level_num - 2 - def _basic_willing_culculate(self, t: float) -> float: """基础意愿值计算""" return math.tan(t * self.expected_replies_per_min * math.pi / 120 / self.number_of_message_storage) / 2 diff --git a/src/person_info/relationship_manager.py b/src/person_info/relationship_manager.py index faa0c77e5..5f5628c18 100644 --- a/src/person_info/relationship_manager.py +++ b/src/person_info/relationship_manager.py @@ -255,8 +255,8 @@ class RelationshipManager: for original_name, mapped_name in name_mapping.items(): points = points.replace(mapped_name, original_name) - # logger.info(f"prompt: {prompt}") - # logger.info(f"points: {points}") + logger.info(f"prompt: {prompt}") + logger.info(f"points: {points}") if not points: logger.warning(f"未能从LLM获取 {person_name} 的新印象") diff --git a/test_log_error.py b/test_log_error.py new file mode 100644 index 000000000..fe9504b1d --- /dev/null +++ b/test_log_error.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +测试日志轮转错误的脚本 +""" + +import logging +import sys +import os +from pathlib import Path + +# 添加src目录到路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) + +from common.logger import get_logger, force_initialize_logging + + +def test_log_rotation_with_error_detection(): + """测试日志轮转并捕获错误""" + print("开始测试日志轮转错误检测...") + + # 强制初始化日志系统 + force_initialize_logging() + + logger = get_logger("error_test") + + # 生成足够多的日志来强制轮转 + large_message = "这是一条用于强制轮转的长消息。" * 200 + + print("开始生成日志以强制轮转...") + + # 监控控制台输出中的错误信息 + original_print = print + errors = [] + + def capture_print(*args, **kwargs): + message = ' '.join(str(arg) for arg in args) + if "重命名失败" in message or "删除失败" in message or "错误" in message: + errors.append(message) + original_print(*args, **kwargs) + + # 临时替换print函数来捕获错误 + import builtins + builtins.print = capture_print + + try: + # 生成大量日志 + for i in range(500): + logger.info(f"错误测试消息 {i}: {large_message}") + if i % 50 == 0: + original_print(f"已生成 {i} 条日志...") + + # 等待一段时间让压缩线程完成 + import time + time.sleep(2) + + finally: + # 恢复原始print函数 + builtins.print = original_print + + print(f"\n检测到的错误信息:") + if errors: + for error in errors: + print(f" - {error}") + else: + print(" 没有检测到错误") + + # 检查日志文件状态 + log_dir = Path("logs") + if log_dir.exists(): + log_files = list(log_dir.glob("app.log*")) + print(f"\n当前日志文件:") + for log_file in sorted(log_files): + size = log_file.stat().st_size / 1024 # KB + print(f" {log_file.name}: {size:.1f} KB") + + return errors + + +if __name__ == "__main__": + errors = test_log_rotation_with_error_detection() + if errors: + print("\n⚠️ 发现错误,需要进一步修复") + sys.exit(1) + else: + print("\n✅ 测试通过,没有发现错误") + sys.exit(0) \ No newline at end of file diff --git a/test_log_fix.py b/test_log_fix.py new file mode 100644 index 000000000..59142a19b --- /dev/null +++ b/test_log_fix.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +测试日志轮转修复的脚本 +""" + +import logging +import time +import threading +from pathlib import Path +import sys +import os + +# 添加src目录到路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) + +from common.logger import get_logger, force_initialize_logging, get_log_stats + + +def test_concurrent_logging(): + """测试并发日志写入""" + logger = get_logger("test") + + def log_worker(worker_id): + """工作线程函数""" + for i in range(100): + logger.info(f"工作线程 {worker_id} - 消息 {i}: 这是一条测试日志消息,用于测试并发写入和轮转功能") + time.sleep(0.01) + + # 创建多个线程并发写入日志 + threads = [] + for i in range(5): + thread = threading.Thread(target=log_worker, args=(i,)) + threads.append(thread) + thread.start() + + # 等待所有线程完成 + for thread in threads: + thread.join() + + print("并发日志测试完成") + + +def test_log_rotation(): + """测试日志轮转""" + logger = get_logger("rotation_test") + + # 生成大量日志来触发轮转 + large_message = "这是一条很长的日志消息用于测试轮转功能。" * 100 + + print("开始生成大量日志以触发轮转...") + for i in range(1000): + logger.info(f"轮转测试消息 {i}: {large_message}") + if i % 100 == 0: + print(f"已生成 {i} 条日志...") + + print("日志轮转测试完成") + + +def main(): + """主函数""" + print("开始测试日志系统修复...") + + # 强制初始化日志系统 + force_initialize_logging() + + # 显示初始日志统计 + stats = get_log_stats() + print(f"初始日志统计: {stats}") + + # 测试并发日志 + print("\n=== 测试并发日志写入 ===") + test_concurrent_logging() + + # 测试日志轮转 + print("\n=== 测试日志轮转 ===") + test_log_rotation() + + # 显示最终日志统计 + stats = get_log_stats() + print(f"\n最终日志统计: {stats}") + + # 检查日志文件 + log_dir = Path("logs") + if log_dir.exists(): + log_files = list(log_dir.glob("app.log*")) + print(f"\n生成的日志文件:") + for log_file in sorted(log_files): + size = log_file.stat().st_size / 1024 / 1024 # MB + print(f" {log_file.name}: {size:.2f} MB") + + print("\n测试完成!如果没有出现权限错误,说明修复成功。") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test_normal_chat_stop.py b/test_normal_chat_stop.py deleted file mode 100644 index 1a08565ae..000000000 --- a/test_normal_chat_stop.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -""" -NormalChat 启动停止测试脚本 -""" - -import asyncio -import logging -from src.common.logger import get_logger - -logger = get_logger("test_normal_chat_stop") - - -async def test_task_cancel_behavior(): - """测试任务取消行为""" - - class MockNormalChat: - def __init__(self): - self._disabled = False - self._chat_task = None - self.stream_name = "test_stream" - - async def mock_reply_loop(self): - """模拟回复循环""" - logger.info("模拟回复循环开始") - try: - while True: - # 检查停用标志 - if self._disabled: - logger.info("检测到停用标志,退出循环") - break - - # 模拟工作 - logger.info("模拟处理消息...") - await asyncio.sleep(0.1) - - except asyncio.CancelledError: - logger.info("模拟回复循环被取消") - raise - except Exception as e: - logger.error(f"模拟回复循环出错: {e}") - finally: - logger.info("模拟回复循环结束") - - async def start_chat(self): - """启动聊天""" - if self._chat_task and not self._chat_task.done(): - logger.info("任务已在运行") - return - - self._disabled = False - self._chat_task = asyncio.create_task(self.mock_reply_loop()) - logger.info("聊天任务已启动") - - async def stop_chat(self): - """停止聊天""" - logger.info("开始停止聊天") - - # 设置停用标志 - self._disabled = True - - if not self._chat_task or self._chat_task.done(): - logger.info("没有运行中的任务") - return - - # 保存任务引用并清空 - task_to_cancel = self._chat_task - self._chat_task = None - - # 取消任务 - task_to_cancel.cancel() - - logger.info("聊天任务停止完成") - - # 测试正常启动停止 - logger.info("=== 测试正常启动停止 ===") - chat = MockNormalChat() - - # 启动 - await chat.start_chat() - await asyncio.sleep(0.5) # 让任务运行一会 - - # 停止 - await chat.stop_chat() - await asyncio.sleep(0.1) # 让取消操作完成 - - logger.info("=== 测试完成 ===") - - -async def main(): - """主函数""" - logger.info("开始 NormalChat 停止测试") - - try: - await test_task_cancel_behavior() - except Exception as e: - logger.error(f"测试失败: {e}") - import traceback - - logger.error(traceback.format_exc()) - - logger.info("测试结束") - - -if __name__ == "__main__": - # 设置日志级别 - logging.basicConfig(level=logging.INFO) - asyncio.run(main())