feat:修复no_reply起始时间,移除normal消息管理器,不再并行生成回复,为focus提供退出方法

This commit is contained in:
SengokuCola
2025-07-12 22:36:08 +08:00
parent 8fae6272bc
commit a549034bbc
6 changed files with 66 additions and 120 deletions

View File

@@ -1,8 +1,7 @@
import asyncio import asyncio
import time import time
import traceback import traceback
from collections import deque from typing import Optional, List
from typing import Optional, Deque, List
from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from rich.traceback import install from rich.traceback import install
@@ -16,9 +15,9 @@ from src.config.config import global_config
from src.person_info.relationship_builder_manager import relationship_builder_manager from src.person_info.relationship_builder_manager import relationship_builder_manager
from src.chat.focus_chat.hfc_utils import CycleDetail from src.chat.focus_chat.hfc_utils import CycleDetail
from random import random from random import random
from src.chat.focus_chat.hfc_utils import create_thinking_message_from_dict, add_messages_to_manager,get_recent_message_stats,cleanup_thinking_message_by_id from src.chat.focus_chat.hfc_utils import get_recent_message_stats
from src.person_info.person_info import get_person_info_manager from src.person_info.person_info import get_person_info_manager
from src.plugin_system.apis import generator_api from src.plugin_system.apis import generator_api,send_api,message_api
from src.chat.willing.willing_manager import get_willing_manager from src.chat.willing.willing_manager import get_willing_manager
from .priority_manager import PriorityManager from .priority_manager import PriorityManager
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat
@@ -201,14 +200,22 @@ class HeartFChatting:
async def _loopbody(self): async def _loopbody(self):
if self.loop_mode == "focus": if self.loop_mode == "focus":
self.energy_value -= 5 * (1/global_config.chat.exit_focus_threshold)
if self.energy_value <= 0:
self.loop_mode = "normal"
return True
return await self._observe() return await self._observe()
elif self.loop_mode == "normal": elif self.loop_mode == "normal":
new_messages_data = get_raw_msg_by_timestamp_with_chat( new_messages_data = get_raw_msg_by_timestamp_with_chat(
chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=time.time(),limit=10,limit_mode="earliest",fliter_bot=True chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=time.time(),limit=10,limit_mode="earliest",fliter_bot=True
) )
if len(new_messages_data) > 5: if len(new_messages_data) > 4 * global_config.chat.auto_focus_threshold:
self.loop_mode = "focus" self.loop_mode = "focus"
self.energy_value = 100
return True return True
if new_messages_data: if new_messages_data:
@@ -228,10 +235,8 @@ class HeartFChatting:
# 创建新的循环信息 # 创建新的循环信息
cycle_timers, thinking_id = self.start_cycle() cycle_timers, thinking_id = self.start_cycle()
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考") logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]")
if message_data:
await create_thinking_message_from_dict(message_data,self.chat_stream,thinking_id)
async with global_prompt_manager.async_message_scope( async with global_prompt_manager.async_message_scope(
self.chat_stream.context.get_template_name() self.chat_stream.context.get_template_name()
@@ -257,7 +262,14 @@ class HeartFChatting:
#如果normal开始一个回复生成进程先准备好回复其实是和planer同时进行的 #如果normal开始一个回复生成进程先准备好回复其实是和planer同时进行的
if self.loop_mode == "normal": if self.loop_mode == "normal":
gen_task = asyncio.create_task(self._generate_normal_response(message_data, available_actions)) person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id(
message_data.get("chat_info_platform"), message_data.get("user_id")
)
person_name = await person_info_manager.get_value(person_id, "person_name")
reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}"
gen_task = asyncio.create_task(self._generate_response(message_data, available_actions,reply_to_str))
with Timer("规划器", cycle_timers): with Timer("规划器", cycle_timers):
@@ -299,6 +311,7 @@ class HeartFChatting:
if action_type == "no_action": if action_type == "no_action":
# 等待回复生成完毕
gather_timeout = global_config.chat.thinking_timeout gather_timeout = global_config.chat.thinking_timeout
try: try:
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
@@ -308,7 +321,7 @@ class HeartFChatting:
if response_set: if response_set:
content = " ".join([item[1] for item in response_set if item[0] == "text"]) content = " ".join([item[1] for item in response_set if item[0] == "text"])
# 模型炸了,没有回复内容生成
if not response_set or ( if not response_set or (
action_type not in ["no_action"] and not is_parallel action_type not in ["no_action"] and not is_parallel
): ):
@@ -318,25 +331,15 @@ class HeartFChatting:
logger.info( logger.info(
f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复"
) )
# 如果模型未生成回复,移除思考消息
await cleanup_thinking_message_by_id(self.chat_stream.stream_id,thinking_id,self.log_prefix)
return False return False
logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}") logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}")
# 提取回复文本
reply_texts = [item[1] for item in response_set if item[0] == "text"]
if not reply_texts:
logger.info(f"[{self.log_prefix}] 回复内容中没有文本,不发送消息")
await cleanup_thinking_message_by_id(self.chat_stream.stream_id,thinking_id,self.log_prefix)
return False
# 发送回复 (不再需要传入 chat) # 发送回复 (不再需要传入 chat)
await add_messages_to_manager(message_data, reply_texts, thinking_id,self.chat_stream.stream_id) await self._send_response(response_set, reply_to_str, loop_start_time)
return response_set if response_set else False return True
@@ -465,7 +468,7 @@ class HeartFChatting:
# 新增:消息计数和疲惫检查 # 新增:消息计数和疲惫检查
if action == "reply" and success: if action == "reply" and success:
self._message_count += 1 self._message_count += 1
current_threshold = self._get_current_fatigue_threshold() current_threshold = max(10, int(30 / global_config.chat.exit_focus_threshold))
logger.info( logger.info(
f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold}" f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold}"
) )
@@ -486,14 +489,6 @@ class HeartFChatting:
return command return command
return "" return ""
def _get_current_fatigue_threshold(self) -> int:
"""动态获取当前的疲惫阈值基于exit_focus_threshold配置
Returns:
int: 当前的疲惫阈值
"""
return max(10, int(30 / global_config.chat.exit_focus_threshold))
async def shutdown(self): async def shutdown(self):
"""优雅关闭HeartFChatting实例取消活动循环任务""" """优雅关闭HeartFChatting实例取消活动循环任务"""
@@ -653,21 +648,14 @@ class HeartFChatting:
return True return True
async def _generate_normal_response( async def _generate_response(
self, message_data: dict, available_actions: Optional[list] self, message_data: dict, available_actions: Optional[list],reply_to:str
) -> Optional[list]: ) -> Optional[list]:
"""生成普通回复""" """生成普通回复"""
try: try:
person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id(
message_data.get("chat_info_platform"), message_data.get("user_id")
)
person_name = await person_info_manager.get_value(person_id, "person_name")
reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}"
success, reply_set = await generator_api.generate_reply( success, reply_set = await generator_api.generate_reply(
chat_stream=self.chat_stream, chat_stream=self.chat_stream,
reply_to=reply_to_str, reply_to=reply_to,
available_actions=available_actions, available_actions=available_actions,
enable_tool=global_config.tool.enable_in_normal_chat, enable_tool=global_config.tool.enable_in_normal_chat,
request_type="normal.replyer", request_type="normal.replyer",
@@ -682,3 +670,37 @@ class HeartFChatting:
except Exception as e: except Exception as e:
logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}")
return None return None
async def _send_response(
self, reply_set, reply_to, thinking_start_time
):
current_time = time.time()
new_message_count = message_api.count_new_messages(
chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time
)
need_reply = new_message_count >= random.randint(2, 4)
logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复"
)
reply_text = ""
first_replyed = False
for reply_seg in reply_set:
data = reply_seg[1]
if not first_replyed:
if need_reply:
await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, typing=False)
first_replyed = True
else:
await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=False)
first_replyed = True
else:
await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=True)
reply_text += data
return reply_text

View File

@@ -7,11 +7,7 @@ from typing import Dict, Any
from src.config.config import global_config from src.config.config import global_config
from src.chat.message_receive.message import MessageThinking from src.chat.message_receive.message import MessageThinking
from src.chat.message_receive.normal_message_sender import message_manager from src.chat.message_receive.normal_message_sender import message_manager
from typing import List
from maim_message import Seg
from src.common.message_repository import count_messages from src.common.message_repository import count_messages
from ..message_receive.message import MessageSending, MessageSet, message_from_db_dict
from src.chat.message_receive.chat_stream import get_chat_manager
@@ -123,71 +119,6 @@ async def cleanup_thinking_message_by_id(chat_id: str, thinking_id: str, log_pre
except Exception as e: except Exception as e:
logger.error(f"{log_prefix} 清理思考消息 {thinking_id} 时出错: {e}") logger.error(f"{log_prefix} 清理思考消息 {thinking_id} 时出错: {e}")
async def add_messages_to_manager(
message_data: dict, response_set: List[str], thinking_id, chat_id
) -> Optional[MessageSending]:
"""发送回复消息"""
chat_stream = get_chat_manager().get_stream(chat_id)
container = await message_manager.get_container(chat_id) # 使用 self.stream_id
thinking_message = None
for msg in container.messages[:]:
# print(msg)
if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id:
thinking_message = msg
container.messages.remove(msg)
break
if not thinking_message:
logger.warning(f"[{chat_id}] 未找到对应的思考消息 {thinking_id},可能已超时被移除")
return None
thinking_start_time = thinking_message.thinking_start_time
message_set = MessageSet(chat_stream, thinking_id) # 使用 self.chat_stream
sender_info = UserInfo(
user_id=message_data.get("user_id"),
user_nickname=message_data.get("user_nickname"),
platform=message_data.get("chat_info_platform"),
)
reply = message_from_db_dict(message_data)
mark_head = False
first_bot_msg = None
for msg in response_set:
if global_config.debug.debug_show_chat_mode:
msg += ""
message_segment = Seg(type="text", data=msg)
bot_message = MessageSending(
message_id=thinking_id,
chat_stream=chat_stream, # 使用 self.chat_stream
bot_user_info=UserInfo(
user_id=global_config.bot.qq_account,
user_nickname=global_config.bot.nickname,
platform=message_data.get("chat_info_platform"),
),
sender_info=sender_info,
message_segment=message_segment,
reply=reply,
is_head=not mark_head,
is_emoji=False,
thinking_start_time=thinking_start_time,
apply_set_reply_logic=True,
)
if not mark_head:
mark_head = True
first_bot_msg = bot_message
message_set.add_message(bot_message)
await message_manager.add_message(message_set)
return first_bot_msg
def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict:

View File

@@ -280,17 +280,12 @@ class NormalChatConfig(ConfigBase):
at_bot_inevitable_reply: bool = False at_bot_inevitable_reply: bool = False
"""@bot 必然回复""" """@bot 必然回复"""
enable_planner: bool = False
"""是否启用动作规划器"""
@dataclass @dataclass
class FocusChatConfig(ConfigBase): class FocusChatConfig(ConfigBase):
"""专注聊天配置类""" """专注聊天配置类"""
think_interval: float = 1
"""思考间隔(秒)"""
consecutive_replies: float = 1 consecutive_replies: float = 1
"""连续回复能力,值越高,麦麦连续回复的概率越高""" """连续回复能力,值越高,麦麦连续回复的概率越高"""

View File

@@ -61,8 +61,8 @@ class NoReplyAction(BaseAction):
count = NoReplyAction._consecutive_count count = NoReplyAction._consecutive_count
reason = self.action_data.get("reason", "") reason = self.action_data.get("reason", "")
start_time = time.time() start_time = self.action_data.get("loop_start_time", time.time())
check_interval = 1.0 # 每秒检查一次 check_interval = 0.6 # 每秒检查一次
# 随机生成本次等待需要的新消息数量阈值 # 随机生成本次等待需要的新消息数量阈值
exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count) exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count)

View File

@@ -98,7 +98,7 @@ class ReplyAction(BaseAction):
) )
# 根据新消息数量决定是否使用reply_to # 根据新消息数量决定是否使用reply_to
need_reply = new_message_count >= random.randint(2, 5) need_reply = new_message_count >= random.randint(2, 4)
logger.info( logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复" f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复"
) )

View File

@@ -119,10 +119,8 @@ willing_mode = "classical" # 回复意愿模式 —— 经典模式classical
response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数 response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数
mentioned_bot_inevitable_reply = true # 提及 bot 必然回复 mentioned_bot_inevitable_reply = true # 提及 bot 必然回复
at_bot_inevitable_reply = true # @bot 必然回复(包含提及) at_bot_inevitable_reply = true # @bot 必然回复(包含提及)
enable_planner = true # 是否启用动作规划器与focus_chat共享actions
[focus_chat] #专注聊天 [focus_chat] #专注聊天
think_interval = 3 # 思考间隔 单位秒,可以有效减少消耗
consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高 consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高
[tool] [tool]