- 移除豆包画图插件,此插件现在插件广场提供 - 修复表达器无法读取原始文本 - 修复normal planner没有超时退出问题

This commit is contained in:
SengokuCola
2025-07-05 22:55:57 +08:00
parent d6d15c36e5
commit 0077bfa77f
12 changed files with 100 additions and 422 deletions

View File

@@ -28,7 +28,6 @@ from .priority_manager import PriorityManager
import traceback
from .normal_chat_generator import NormalChatGenerator
from src.chat.normal_chat.normal_chat_expressor import NormalChatExpressor
from src.chat.normal_chat.normal_chat_planner import NormalChatPlanner
from src.chat.normal_chat.normal_chat_action_modifier import NormalChatActionModifier
@@ -72,9 +71,6 @@ class NormalChat:
self.stream_name = get_chat_manager().get_stream_name(self.stream_id) or self.stream_id
# 初始化Normal Chat专用表达器
self.expressor = NormalChatExpressor(self.chat_stream)
# Interest dict
self.interest_dict = interest_dict
@@ -120,6 +116,8 @@ class NormalChat:
self.get_cooldown_progress_callback = get_cooldown_progress_callback
self._disabled = False # 增加停用标志
self.timeout_count = 0
# 加载持久化的缓存
self._load_cache()
@@ -490,14 +488,10 @@ class NormalChat:
logger.info(
f"[{self.stream_name}] 从队列中取出消息进行处理: User {message.message_info.user_info.user_id}, Time: {time.strftime('%H:%M:%S', time.localtime(message.message_info.time))}"
)
# 执行定期清理
self._cleanup_old_segments()
# 更新消息段信息
self._update_user_message_segments(message)
# 检查是否有用户满足关系构建条件
asyncio.create_task(self._check_relation_building_conditions())
asyncio.create_task(self._check_relation_building_conditions(message))
await self.reply_one_message(message)
@@ -722,18 +716,9 @@ class NormalChat:
if self.priority_manager:
self.priority_manager.add_message(message)
return
# --- 以下为原有的 "兴趣" 模式逻辑 ---
await self._process_message(message, is_mentioned, interested_rate)
async def _process_message(self, message: MessageRecv, is_mentioned: bool, interested_rate: float) -> None:
"""
实际处理单条消息的逻辑,包括意愿判断、回复生成、动作执行等。
"""
if self._disabled:
return
# 新增在auto模式下检查是否需要直接切换到focus模式
# 新增在auto模式下检查是否需要直接切换到focus模式
if global_config.chat.chat_mode == "auto":
if await self._check_should_switch_to_focus():
logger.info(f"[{self.stream_name}] 检测到切换到focus聊天模式的条件尝试执行切换")
@@ -747,14 +732,20 @@ class NormalChat:
else:
logger.warning(f"[{self.stream_name}] 没有设置切换到focus聊天模式的回调函数无法执行切换")
# 执行定期清理
self._cleanup_old_segments()
# --- 以下为原有的 "兴趣" 模式逻辑 ---
await self._process_message(message, is_mentioned, interested_rate)
async def _process_message(self, message: MessageRecv, is_mentioned: bool, interested_rate: float) -> None:
"""
实际处理单条消息的逻辑,包括意愿判断、回复生成、动作执行等。
"""
if self._disabled:
return
# 更新消息段信息
self._update_user_message_segments(message)
# 检查是否有用户满足关系构建条件
asyncio.create_task(self._check_relation_building_conditions())
asyncio.create_task(self._check_relation_building_conditions(message))
timing_results = {}
reply_probability = (
@@ -776,6 +767,10 @@ class NormalChat:
reply_probability += message.message_info.additional_config["maimcore_reply_probability_gain"]
reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间
# 处理表情包
if message.is_emoji or message.is_picid:
reply_probability = 0
# 应用疲劳期回复频率调整
fatigue_multiplier = self._get_fatigue_reply_multiplier()
original_probability = reply_probability
@@ -804,6 +799,8 @@ class NormalChat:
await willing_manager.before_generate_reply_handle(message.message_info.message_id)
do_reply = await self.reply_one_message(message)
response_set = do_reply if do_reply else None
# 输出性能计时结果
if do_reply and response_set: # 确保 response_set 不是 None
timing_str = " | ".join([f"{step}: {duration:.2f}" for step, duration in timing_results.items()])
@@ -855,8 +852,6 @@ class NormalChat:
return None
try:
# 获取发送者名称(动作修改已在并行执行前完成)
sender_name = self._get_sender_name(message)
no_action = {
"action_result": {
@@ -876,7 +871,7 @@ class NormalChat:
return no_action
# 执行规划
plan_result = await self.planner.plan(message, sender_name)
plan_result = await self.planner.plan(message)
action_type = plan_result["action_result"]["action_type"]
action_data = plan_result["action_result"]["action_data"]
reasoning = plan_result["action_result"]["reasoning"]
@@ -914,9 +909,35 @@ class NormalChat:
# 并行执行回复生成和动作规划
self.action_type = None # 初始化动作类型
self.is_parallel_action = False # 初始化并行动作标志
response_set, plan_result = await asyncio.gather(
generate_normal_response(), plan_and_execute_actions(), return_exceptions=True
)
gen_task = asyncio.create_task(generate_normal_response())
plan_task = asyncio.create_task(plan_and_execute_actions())
try:
gather_timeout = global_config.normal_chat.thinking_timeout
results = await asyncio.wait_for(
asyncio.gather(gen_task, plan_task, return_exceptions=True),
timeout=gather_timeout,
)
response_set, plan_result = results
except asyncio.TimeoutError:
logger.warning(f"[{self.stream_name}] 并行执行回复生成和动作规划超时 ({gather_timeout}秒),正在取消相关任务...")
self.timeout_count += 1
if self.timeout_count > 5:
logger.error(f"[{self.stream_name}] 连续回复超时,{global_config.normal_chat.thinking_timeout}秒 内大模型没有返回有效内容请检查你的api是否速度过慢或配置错误。建议不要使用推理模型推理模型生成速度过慢。")
return False
# 取消未完成的任务
if not gen_task.done():
gen_task.cancel()
if not plan_task.done():
plan_task.cancel()
# 清理思考消息
await self._cleanup_thinking_message_by_id(thinking_id)
response_set = None
plan_result = None
# 处理生成回复的结果
if isinstance(response_set, Exception):
@@ -937,14 +958,7 @@ class NormalChat:
elif self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action:
logger.info(f"[{self.stream_name}] 模型选择其他动作(非并行动作)")
# 如果模型未生成回复,移除思考消息
container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id
for msg in container.messages[:]:
if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id:
container.messages.remove(msg)
logger.debug(f"[{self.stream_name}] 已移除未产生回复的思考消息 {thinking_id}")
break
# 需要在此处也调用 not_reply_handle 和 delete 吗?
# 如果是因为模型没回复,也算是一种 "未回复"
await self._cleanup_thinking_message_by_id(thinking_id)
return False
# logger.info(f"[{self.stream_name}] 回复内容: {response_set}")
@@ -969,9 +983,7 @@ class NormalChat:
"user_nickname": message.message_info.user_info.user_nickname,
},
"response": response_set,
# "is_mentioned": is_mentioned,
"is_reference_reply": message.reply is not None, # 判断是否为引用回复
# "timing": {k: round(v, 2) for k, v in timing_results.items()},
}
self.recent_replies.append(reply_info)
# 保持最近回复历史在限定数量内
@@ -1198,18 +1210,6 @@ class NormalChat:
f"意愿放大器更新为: {self.willing_amplifier:.2f}"
)
def _get_sender_name(self, message: MessageRecv) -> str:
"""获取发送者名称用于planner"""
if message.chat_stream.user_info:
user_info = message.chat_stream.user_info
if user_info.user_cardname and user_info.user_nickname:
return f"[{user_info.user_nickname}][群昵称:{user_info.user_cardname}]"
elif user_info.user_nickname:
return f"[{user_info.user_nickname}]"
else:
return f"用户({user_info.user_id})"
return "某人"
async def _execute_action(
self, action_type: str, action_data: dict, message: MessageRecv, thinking_id: str
) -> Optional[bool]:
@@ -1246,17 +1246,18 @@ class NormalChat:
return False
def set_planner_enabled(self, enabled: bool):
"""设置是否启用planner"""
self.enable_planner = enabled
logger.info(f"[{self.stream_name}] Planner {'启用' if enabled else '禁用'}")
def get_action_manager(self) -> ActionManager:
"""获取动作管理器实例"""
return self.action_manager
async def _check_relation_building_conditions(self):
async def _check_relation_building_conditions(self, message: MessageRecv):
"""检查person_engaged_cache中是否有满足关系构建条件的用户"""
# 执行定期清理
self._cleanup_old_segments()
# 更新消息段信息
self._update_user_message_segments(message)
users_to_build_relationship = []
for person_id, segments in list(self.person_engaged_cache.items()):
@@ -1401,3 +1402,16 @@ class NormalChat:
)
return should_switch
async def _cleanup_thinking_message_by_id(self, thinking_id: str):
"""根据ID清理思考消息"""
try:
container = await message_manager.get_container(self.stream_id)
if container:
for msg in container.messages[:]:
if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id:
container.messages.remove(msg)
logger.info(f"[{self.stream_name}] 已清理思考消息 {thinking_id}")
break
except Exception as e:
logger.error(f"[{self.stream_name}] 清理思考消息 {thinking_id} 时出错: {e}")

View File

@@ -1,262 +0,0 @@
"""
Normal Chat Expressor
为Normal Chat专门设计的表达器不需要经过LLM风格化处理
直接发送消息,主要用于插件动作中需要发送消息的场景。
"""
import time
from typing import List, Optional, Tuple, Dict, Any
from src.chat.message_receive.message import MessageRecv, MessageSending, MessageThinking, Seg
from src.chat.message_receive.message import UserInfo
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
from src.chat.message_receive.message_sender import message_manager
from src.config.config import global_config
from src.common.logger import get_logger
logger = get_logger("normal_chat_expressor")
class NormalChatExpressor:
"""Normal Chat专用表达器
特点:
1. 不经过LLM风格化直接发送消息
2. 支持文本和表情包发送
3. 为插件动作提供简化的消息发送接口
4. 保持与focus_chat expressor相似的API但去掉复杂的风格化流程
"""
def __init__(self, chat_stream: ChatStream):
"""初始化Normal Chat表达器
Args:
chat_stream: 聊天流对象
stream_name: 流名称
"""
self.chat_stream = chat_stream
self.stream_name = get_chat_manager().get_stream_name(self.chat_stream.stream_id) or self.chat_stream.stream_id
self.log_prefix = f"[{self.stream_name}]Normal表达器"
logger.debug(f"{self.log_prefix} 初始化完成")
async def create_thinking_message(
self, anchor_message: Optional[MessageRecv], thinking_id: str
) -> Optional[MessageThinking]:
"""创建思考消息
Args:
anchor_message: 锚点消息
thinking_id: 思考ID
Returns:
MessageThinking: 创建的思考消息如果失败返回None
"""
if not anchor_message or not anchor_message.chat_stream:
logger.error(f"{self.log_prefix} 无法创建思考消息,缺少有效的锚点消息或聊天流")
return None
messageinfo = anchor_message.message_info
thinking_time_point = time.time()
bot_user_info = UserInfo(
user_id=global_config.bot.qq_account,
user_nickname=global_config.bot.nickname,
platform=messageinfo.platform,
)
thinking_message = MessageThinking(
message_id=thinking_id,
chat_stream=self.chat_stream,
bot_user_info=bot_user_info,
reply=anchor_message,
thinking_start_time=thinking_time_point,
)
await message_manager.add_message(thinking_message)
logger.debug(f"{self.log_prefix} 创建思考消息: {thinking_id}")
return thinking_message
async def send_response_messages(
self,
anchor_message: Optional[MessageRecv],
response_set: List[Tuple[str, str]],
thinking_id: str = "",
display_message: str = "",
) -> Optional[MessageSending]:
"""发送回复消息
Args:
anchor_message: 锚点消息
response_set: 回复内容集合,格式为 [(type, content), ...]
thinking_id: 思考ID
display_message: 显示消息
Returns:
MessageSending: 发送的第一条消息如果失败返回None
"""
try:
if not response_set:
logger.warning(f"{self.log_prefix} 回复内容为空")
return None
# 如果没有thinking_id生成一个
if not thinking_id:
thinking_time_point = round(time.time(), 2)
thinking_id = "mt" + str(thinking_time_point)
# 创建思考消息
if anchor_message:
await self.create_thinking_message(anchor_message, thinking_id)
# 创建消息集
mark_head = False
is_emoji = False
if len(response_set) == 0:
return None
message_id = f"{thinking_id}_{len(response_set)}"
response_type, content = response_set[0]
if len(response_set) > 1:
message_segment = Seg(type="seglist", data=[Seg(type=t, data=c) for t, c in response_set])
else:
message_segment = Seg(type=response_type, data=content)
if response_type == "emoji":
is_emoji = True
bot_msg = await self._build_sending_message(
message_id=message_id,
message_segment=message_segment,
thinking_id=thinking_id,
anchor_message=anchor_message,
thinking_start_time=time.time(),
reply_to=mark_head,
is_emoji=is_emoji,
display_message=display_message,
)
logger.debug(f"{self.log_prefix} 添加{response_type}类型消息: {content}")
# 提交消息集
if bot_msg:
await message_manager.add_message(bot_msg)
logger.info(
f"{self.log_prefix} 成功发送 {response_type}类型消息: {str(content)[:200] + '...' if len(str(content)) > 200 else content}"
)
container = await message_manager.get_container(self.chat_stream.stream_id) # 使用 self.stream_id
for msg in container.messages[:]:
if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id:
container.messages.remove(msg)
logger.debug(f"[{self.stream_name}] 已移除未产生回复的思考消息 {thinking_id}")
break
return bot_msg
else:
logger.warning(f"{self.log_prefix} 没有有效的消息被创建")
return None
except Exception as e:
logger.error(f"{self.log_prefix} 发送消息失败: {e}")
import traceback
traceback.print_exc()
return None
async def _build_sending_message(
self,
message_id: str,
message_segment: Seg,
thinking_id: str,
anchor_message: Optional[MessageRecv],
thinking_start_time: float,
reply_to: bool = False,
is_emoji: bool = False,
display_message: str = "",
) -> MessageSending:
"""构建发送消息
Args:
message_id: 消息ID
message_segment: 消息段
thinking_id: 思考ID
anchor_message: 锚点消息
thinking_start_time: 思考开始时间
reply_to: 是否回复
is_emoji: 是否为表情包
Returns:
MessageSending: 构建的发送消息
"""
bot_user_info = UserInfo(
user_id=global_config.bot.qq_account,
user_nickname=global_config.bot.nickname,
platform=anchor_message.message_info.platform if anchor_message else "unknown",
)
message_sending = MessageSending(
message_id=message_id,
chat_stream=self.chat_stream,
bot_user_info=bot_user_info,
message_segment=message_segment,
sender_info=self.chat_stream.user_info,
reply=anchor_message if reply_to else None,
thinking_start_time=thinking_start_time,
is_emoji=is_emoji,
display_message=display_message,
)
return message_sending
async def deal_reply(
self,
cycle_timers: dict,
action_data: Dict[str, Any],
reasoning: str,
anchor_message: MessageRecv,
thinking_id: str,
) -> Tuple[bool, Optional[str]]:
"""处理回复动作 - 兼容focus_chat expressor API
Args:
cycle_timers: 周期计时器normal_chat中不使用
action_data: 动作数据包含text、target、emojis等
reasoning: 推理说明
anchor_message: 锚点消息
thinking_id: 思考ID
Returns:
Tuple[bool, Optional[str]]: (是否成功, 回复文本)
"""
try:
response_set = []
# 处理文本内容
text_content = action_data.get("text", "")
if text_content:
response_set.append(("text", text_content))
# 处理表情包
emoji_content = action_data.get("emojis", "")
if emoji_content:
response_set.append(("emoji", emoji_content))
if not response_set:
logger.warning(f"{self.log_prefix} deal_reply: 没有有效的回复内容")
return False, None
# 发送消息
result = await self.send_response_messages(
anchor_message=anchor_message,
response_set=response_set,
thinking_id=thinking_id,
)
if result:
return True, text_content if text_content else "发送成功"
else:
return False, None
except Exception as e:
logger.error(f"{self.log_prefix} deal_reply执行失败: {e}")
import traceback
traceback.print_exc()
return False, None

View File

@@ -72,7 +72,7 @@ class NormalChatPlanner:
self.action_manager = action_manager
async def plan(self, message: MessageThinking, sender_name: str = "某人") -> Dict[str, Any]:
async def plan(self, message: MessageThinking) -> Dict[str, Any]:
"""
Normal Chat 规划器: 使用LLM根据上下文决定做出什么动作。

View File

@@ -33,28 +33,10 @@ class ClassicalWillingManager(BaseWillingManager):
if willing_info.is_mentioned_bot:
current_willing += 1 if current_willing < 1.0 else 0.05
is_emoji_not_reply = False
if willing_info.is_emoji:
if global_config.normal_chat.emoji_response_penalty != 0:
current_willing *= global_config.normal_chat.emoji_response_penalty
else:
is_emoji_not_reply = True
# 处理picid格式消息直接不回复
is_picid_not_reply = False
if willing_info.is_picid:
is_picid_not_reply = True
self.chat_reply_willing[chat_id] = min(current_willing, 3.0)
reply_probability = min(max((current_willing - 0.5), 0.01) * 2, 1)
if is_emoji_not_reply:
reply_probability = 0
if is_picid_not_reply:
reply_probability = 0
return reply_probability
async def before_generate_reply_handle(self, message_id):
@@ -71,8 +53,6 @@ class ClassicalWillingManager(BaseWillingManager):
if current_willing < 1:
self.chat_reply_willing[chat_id] = min(1.0, current_willing + 0.4)
async def bombing_buffer_message_handle(self, message_id):
return await super().bombing_buffer_message_handle(message_id)
async def not_reply_handle(self, message_id):
return await super().not_reply_handle(message_id)

View File

@@ -17,8 +17,5 @@ class CustomWillingManager(BaseWillingManager):
async def get_reply_probability(self, message_id: str):
pass
async def bombing_buffer_message_handle(self, message_id: str):
pass
def __init__(self):
super().__init__()

View File

@@ -19,7 +19,6 @@ Mxp 模式:梦溪畔独家赞助
下下策是询问一个菜鸟(@梦溪畔)
"""
from src.config.config import global_config
from .willing_manager import BaseWillingManager
from typing import Dict
import asyncio
@@ -172,23 +171,11 @@ class MxpWillingManager(BaseWillingManager):
self.logger.debug("进行中消息惩罚归0")
probability = self._willing_to_probability(current_willing)
if w_info.is_emoji:
probability *= global_config.normal_chat.emoji_response_penalty
if w_info.is_picid:
probability = 0 # picid格式消息直接不回复
self.temporary_willing = current_willing
return probability
async def bombing_buffer_message_handle(self, message_id: str):
"""炸飞消息处理"""
async with self.lock:
w_info = self.ongoing_messages[message_id]
self.chat_person_reply_willing[w_info.chat_id][w_info.person_id] += 0.1
async def _return_to_basic_willing(self):
"""使每个人的意愿恢复到chat基础意愿"""
while True:

View File

@@ -20,7 +20,6 @@ before_generate_reply_handle 确定要回复后,在生成回复前的处理
after_generate_reply_handle 确定要回复后,在生成回复后的处理
not_reply_handle 确定不回复后的处理
get_reply_probability 获取回复概率
bombing_buffer_message_handle 缓冲器炸飞消息后的处理
get_variable_parameters 暂不确定
set_variable_parameters 暂不确定
以下2个方法根据你的实现可以做调整
@@ -137,10 +136,6 @@ class BaseWillingManager(ABC):
"""抽象方法:获取回复概率"""
raise NotImplementedError
@abstractmethod
async def bombing_buffer_message_handle(self, message_id: str):
"""抽象方法:炸飞消息处理"""
pass
async def get_willing(self, chat_id: str):
"""获取指定聊天流的回复意愿"""