Merge branch 'dev' of github.com:MaiM-with-u/MaiBot into dev

This commit is contained in:
UnCLAS-Prommer
2025-08-11 18:54:19 +08:00
24 changed files with 589 additions and 1225 deletions

View File

@@ -166,10 +166,10 @@ temperature = 0.7
max_tokens = 800
```
### replyer_1 - 主要回复模型
### replyer - 主要回复模型
首要回复模型,也用于表达器和表达方式学习:
```toml
[model_task_config.replyer_1]
[model_task_config.replyer]
model_list = ["siliconflow-deepseek-v3"]
temperature = 0.2
max_tokens = 800

View File

@@ -21,10 +21,9 @@ from src.person_info.person_info import get_person_info_manager
from src.plugin_system.base.component_types import ActionInfo, ChatMode, EventType
from src.plugin_system.core import events_manager
from src.plugin_system.apis import generator_api, send_api, message_api, database_api
from src.chat.willing.willing_manager import get_willing_manager
from src.mais4u.mai_think import mai_thinking_manager
from src.mais4u.constant_s4u import ENABLE_S4U
import math
# no_reply逻辑已集成到heartFC_chat.py中不再需要导入
from src.chat.chat_loop.hfc_utils import send_typing, stop_typing
@@ -91,6 +90,7 @@ class HeartFChatting:
self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id)
self.expression_learner = expression_learner_manager.get_expression_learner(self.stream_id)
self.action_manager = ActionManager()
self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager)
self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id)
@@ -98,7 +98,6 @@ class HeartFChatting:
# 循环控制内部状态
self.running: bool = False
self._loop_task: Optional[asyncio.Task] = None # 主循环任务
self._energy_task: Optional[asyncio.Task] = None
# 添加循环信息管理相关的属性
self.history_loop: List[CycleDetail] = []
@@ -110,12 +109,6 @@ class HeartFChatting:
self.last_read_time = time.time() - 1
self.willing_manager = get_willing_manager()
logger.info(f"{self.log_prefix} HeartFChatting 初始化完成")
self.energy_value = 5
self.focus_energy = 1
self.no_reply_consecutive = 0
# 最近三次no_reply的新消息兴趣度记录
@@ -133,9 +126,6 @@ class HeartFChatting:
# 标记为活动状态,防止重复启动
self.running = True
self._energy_task = asyncio.create_task(self._energy_loop())
self._energy_task.add_done_callback(self._handle_energy_completion)
self._loop_task = asyncio.create_task(self._main_chat_loop())
self._loop_task.add_done_callback(self._handle_loop_completion)
logger.info(f"{self.log_prefix} HeartFChatting 启动完成")
@@ -171,19 +161,6 @@ class HeartFChatting:
self._current_cycle_detail.timers = cycle_timers
self._current_cycle_detail.end_time = time.time()
def _handle_energy_completion(self, task: asyncio.Task):
if exception := task.exception():
logger.error(f"{self.log_prefix} HeartFChatting: 能量循环异常: {exception}")
logger.error(traceback.format_exc())
else:
logger.info(f"{self.log_prefix} HeartFChatting: 能量循环完成")
async def _energy_loop(self):
while self.running:
await asyncio.sleep(12)
self.energy_value -= 0.5
self.energy_value = max(self.energy_value, 0.3)
def print_cycle_info(self, cycle_timers):
# 记录循环信息和计时器结果
timer_strings = []
@@ -193,27 +170,28 @@ class HeartFChatting:
# 获取动作类型,兼容新旧格式
action_type = "未知动作"
if hasattr(self, "_current_cycle_detail") and self._current_cycle_detail:
if hasattr(self, '_current_cycle_detail') and self._current_cycle_detail:
loop_plan_info = self._current_cycle_detail.loop_plan_info
if isinstance(loop_plan_info, dict):
action_result = loop_plan_info.get("action_result", {})
action_result = loop_plan_info.get('action_result', {})
if isinstance(action_result, dict):
# 旧格式action_result是字典
action_type = action_result.get("action_type", "未知动作")
action_type = action_result.get('action_type', '未知动作')
elif isinstance(action_result, list) and action_result:
# 新格式action_result是actions列表
action_type = action_result[0].get("action_type", "未知动作")
action_type = action_result[0].get('action_type', '未知动作')
elif isinstance(loop_plan_info, list) and loop_plan_info:
# 直接是actions列表的情况
action_type = loop_plan_info[0].get("action_type", "未知动作")
action_type = loop_plan_info[0].get('action_type', '未知动作')
logger.info(
f"{self.log_prefix}{self._current_cycle_detail.cycle_id}次思考,"
f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " # type: ignore
f"选择动作: {action_type}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
f"选择动作: {action_type}"
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
)
def _determine_form_type(self):
def _determine_form_type(self) -> str:
"""判断使用哪种形式的no_reply"""
# 如果连续no_reply次数少于3次使用waiting形式
if self.no_reply_consecutive <= 3:
@@ -223,21 +201,19 @@ class HeartFChatting:
total_recent_interest = sum(self.recent_interest_records)
# 计算调整后的阈值
adjusted_threshold = 3 / global_config.chat.get_current_talk_frequency(self.stream_id)
adjusted_threshold = 1 / global_config.chat.get_current_talk_frequency(self.stream_id)
logger.info(
f"{self.log_prefix} 最近三次兴趣度总和: {total_recent_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}"
)
logger.info(f"{self.log_prefix} 最近三次兴趣度总和: {total_recent_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}")
# 如果兴趣度总和小于阈值进入breaking形式
if total_recent_interest < adjusted_threshold:
logger.info(f"{self.log_prefix} 兴趣度不足,进入breaking形式")
logger.info(f"{self.log_prefix} 兴趣度不足,进入休息")
self.focus_energy = random.randint(3, 6)
else:
logger.info(f"{self.log_prefix} 兴趣度充足")
logger.info(f"{self.log_prefix} 兴趣度充足,等待新消息")
self.focus_energy = 1
async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool, float]:
async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool,float]:
"""
判断是否应该处理消息
@@ -249,10 +225,10 @@ class HeartFChatting:
bool: 是否应该处理消息
"""
new_message_count = len(new_message)
talk_frequency = global_config.chat.get_current_talk_frequency(self.stream_id)
# talk_frequency = global_config.chat.get_current_talk_frequency(self.stream_id)
modified_exit_count_threshold = self.focus_energy / global_config.chat.focus_value
modified_exit_count_threshold = self.focus_energy * 0.5 / talk_frequency
modified_exit_interest_threshold = 1.5 / talk_frequency
total_interest = 0.0
for msg_dict in new_message:
interest_value = msg_dict.get("interest_value", 0.0)
@@ -260,34 +236,28 @@ class HeartFChatting:
total_interest += interest_value
if new_message_count >= modified_exit_count_threshold:
# 记录兴趣度到列表
self.recent_interest_records.append(total_interest)
logger.info(
f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold:.1f}),结束等待"
)
logger.info(str(self.last_read_time))
logger.info(str(new_message))
return True, total_interest / new_message_count
# logger.info(self.last_read_time)
# logger.info(new_message)
return True,total_interest/new_message_count
# 检查累计兴趣值
if new_message_count > 0:
# 只在兴趣值变化时输出log
if not hasattr(self, "_last_accumulated_interest") or total_interest != self._last_accumulated_interest:
logger.info(
f"{self.log_prefix} breaking形式当前累计兴趣值: {total_interest:.2f}, 专注度: {global_config.chat.focus_value:.1f}"
)
logger.info(f"{self.log_prefix} 休息中,累计兴趣值: {total_interest:.2f}, 活跃度: {talk_frequency:.1f}")
self._last_accumulated_interest = total_interest
if total_interest >= 3 / global_config.chat.focus_value:
if total_interest >= modified_exit_interest_threshold:
# 记录兴趣度到列表
self.recent_interest_records.append(total_interest)
logger.info(
f"{self.log_prefix} 累计兴趣值达到{total_interest:.2f}(>{3 / global_config.chat.focus_value}),结束等待"
f"{self.log_prefix} 累计兴趣值达到{total_interest:.2f}(>{modified_exit_interest_threshold:.1f}),结束等待"
)
return True, total_interest / new_message_count
return True,total_interest/new_message_count
# 每10秒输出一次等待状态
if int(time.time() - self.last_read_time) > 0 and int(time.time() - self.last_read_time) % 10 == 0:
@@ -296,64 +266,46 @@ class HeartFChatting:
)
await asyncio.sleep(0.5)
return False, 0.0
return False,0.0
async def _loopbody(self):
recent_messages_dict = message_api.get_messages_by_time_in_chat(
chat_id=self.stream_id,
start_time=self.last_read_time,
end_time=time.time(),
limit=10,
limit = 10,
limit_mode="latest",
filter_mai=True,
filter_command=True,
)
# 统一的消息处理逻辑
should_process, interest_value = await self._should_process_messages(recent_messages_dict)
should_process,interest_value = await self._should_process_messages(recent_messages_dict)
if should_process:
# earliest_message_data = recent_messages_dict[0]
# self.last_read_time = earliest_message_data.get("time")
self.last_read_time = time.time()
await self._observe(interest_value=interest_value)
await self._observe(interest_value = interest_value)
else:
# Normal模式消息数量不足等待
await asyncio.sleep(0.5)
return True
return True
async def build_reply_to_str(self, message_data: dict):
person_info_manager = get_person_info_manager()
# 获取 platform如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值
platform = message_data.get("chat_info_platform")
if platform is None:
platform = getattr(self.chat_stream, "platform", "unknown")
person_id = person_info_manager.get_person_id(
platform, # type: ignore
message_data.get("user_id"), # type: ignore
)
person_name = await person_info_manager.get_value(person_id, "person_name")
return f"{person_name}:{message_data.get('processed_plain_text')}"
async def _send_and_store_reply(
self,
response_set,
reply_to_str,
loop_start_time,
action_message,
cycle_timers: Dict[str, float],
thinking_id,
actions,
) -> Tuple[Dict[str, Any], str, Dict[str, float]]:
with Timer("回复发送", cycle_timers):
reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, action_message)
# 存储reply action信息
with Timer("回复发送", cycle_timers):
reply_text = await self._send_response(response_set, action_message)
# 存储reply action信息
person_info_manager = get_person_info_manager()
# 获取 platform如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值
@@ -374,7 +326,7 @@ class HeartFChatting:
action_prompt_display=action_prompt_display,
action_done=True,
thinking_id=thinking_id,
action_data={"reply_text": reply_text, "reply_to": reply_to_str},
action_data={"reply_text": reply_text},
action_name="reply",
)
@@ -393,14 +345,11 @@ class HeartFChatting:
return loop_info, reply_text, cycle_timers
async def _observe(self, interest_value: float = 0.0) -> bool:
async def _observe(self,interest_value:float = 0.0) -> bool:
action_type = "no_action"
reply_text = "" # 初始化reply_text变量避免UnboundLocalError
# 根据interest_value计算概率决定使用哪种planner模式
# interest_value越高越倾向于使用Normal模式
import random
import math
# 使用sigmoid函数将interest_value转换为概率
# 当interest_value为0时概率接近0使用Focus模式
@@ -415,21 +364,15 @@ class HeartFChatting:
x0 = 1.0 # 控制曲线中心点
return 1.0 / (1.0 + math.exp(-k * (interest_val - x0)))
normal_mode_probability = calculate_normal_mode_probability(
interest_value
) / global_config.chat.get_current_talk_frequency(self.stream_id)
normal_mode_probability = calculate_normal_mode_probability(interest_value) * 0.5 / global_config.chat.get_current_talk_frequency(self.stream_id)
# 根据概率决定使用哪种模式
if random.random() < normal_mode_probability:
mode = ChatMode.NORMAL
logger.info(
f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Normal planner模式"
)
logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Normal planner模式")
else:
mode = ChatMode.FOCUS
logger.info(
f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Focus planner模式"
)
logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Focus planner模式")
# 创建新的循环信息
cycle_timers, thinking_id = self.start_cycle()
@@ -440,45 +383,48 @@ class HeartFChatting:
await send_typing()
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
loop_start_time = time.time()
await self.relationship_builder.build_relation()
await self.expression_learner.trigger_learning_for_chat()
available_actions = {}
# 第一步:动作修改
with Timer("动作修改", cycle_timers):
try:
await self.action_modifier.modify_actions()
available_actions = self.action_manager.get_using_actions()
except Exception as e:
logger.error(f"{self.log_prefix} 动作修改失败: {e}")
if random.random() > global_config.chat.focus_value and mode == ChatMode.FOCUS:
#如果激活度没有激活并且聊天活跃度低有可能不进行plan相当于不在电脑前
actions = [
{
"action_type": "no_reply",
"reasoning": "选择不回复",
"action_data": {},
}
]
else:
available_actions = {}
# 第一步:动作修改
with Timer("动作修改", cycle_timers):
try:
await self.action_modifier.modify_actions()
available_actions = self.action_manager.get_using_actions()
except Exception as e:
logger.error(f"{self.log_prefix} 动作修改失败: {e}")
# 执行planner
planner_info = self.action_planner.get_necessary_info()
prompt_info = await self.action_planner.build_planner_prompt(
is_group_chat=planner_info[0],
chat_target_info=planner_info[1],
current_available_actions=planner_info[2],
)
if not await events_manager.handle_mai_events(
EventType.ON_PLAN, None, prompt_info[0], None, self.chat_stream.stream_id
):
return False
with Timer("规划器", cycle_timers):
actions, _ = await self.action_planner.plan(
mode=mode,
loop_start_time=loop_start_time,
available_actions=available_actions,
# 执行planner
planner_info = self.action_planner.get_necessary_info()
prompt_info = await self.action_planner.build_planner_prompt(
is_group_chat=planner_info[0],
chat_target_info=planner_info[1],
current_available_actions=planner_info[2],
)
if not await events_manager.handle_mai_events(
EventType.ON_PLAN, None, prompt_info[0], None, self.chat_stream.stream_id
):
return False
with Timer("规划器", cycle_timers):
actions, _= await self.action_planner.plan(
mode=mode,
loop_start_time=self.last_read_time,
available_actions=available_actions,
)
# action_result: Dict[str, Any] = plan_result.get("action_result", {}) # type: ignore
# action_type, action_data, reasoning, is_parallel = (
# action_result.get("action_type", "error"),
# action_result.get("action_data", {}),
# action_result.get("reasoning", "未提供理由"),
# action_result.get("is_parallel", True),
# )
# 3. 并行执行所有动作
async def execute_action(action_info):
@@ -500,7 +446,12 @@ class HeartFChatting:
action_name="no_reply",
)
return {"action_type": "no_reply", "success": True, "reply_text": "", "command": ""}
return {
"action_type": "no_reply",
"success": True,
"reply_text": "",
"command": ""
}
elif action_info["action_type"] != "reply":
# 执行普通动作
with Timer("动作执行", cycle_timers):
@@ -510,47 +461,47 @@ class HeartFChatting:
action_info["action_data"],
cycle_timers,
thinking_id,
action_info["action_message"],
action_info["action_message"]
)
return {
"action_type": action_info["action_type"],
"success": success,
"reply_text": reply_text,
"command": command,
"command": command
}
else:
# 执行回复动作
reply_to_str = await self.build_reply_to_str(action_info["action_message"])
# 生成回复
gather_timeout = global_config.chat.thinking_timeout
try:
response_set = await asyncio.wait_for(
self._generate_response(
message_data=action_info["action_message"],
available_actions=action_info["available_actions"],
reply_to=reply_to_str,
request_type="chat.replyer",
),
timeout=gather_timeout,
success, response_set, _ = await generator_api.generate_reply(
chat_stream=self.chat_stream,
reply_message = action_info["action_message"],
available_actions=available_actions,
reply_reason=action_info.get("reasoning", ""),
enable_tool=global_config.tool.enable_tool,
request_type="chat.replyer",
from_plugin=False,
)
except asyncio.TimeoutError:
logger.warning(
f"{self.log_prefix} 并行执行:回复生成超时>{global_config.chat.thinking_timeout}s已跳过"
)
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
if not success or not response_set:
logger.info(f"{action_info['action_message'].get('processed_plain_text')} 的回复生成失败")
return {
"action_type": "reply",
"success": False,
"reply_text": "",
"loop_info": None
}
except asyncio.CancelledError:
logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消")
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
if not response_set:
logger.warning(f"{self.log_prefix} 模型超时或生成回复内容为空")
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}
return {
"action_type": "reply",
"success": False,
"reply_text": "",
"loop_info": None
}
loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply(
response_set,
reply_to_str,
loop_start_time,
action_info["action_message"],
cycle_timers,
thinking_id,
@@ -560,7 +511,7 @@ class HeartFChatting:
"action_type": "reply",
"success": True,
"reply_text": reply_text,
"loop_info": loop_info,
"loop_info": loop_info
}
except Exception as e:
logger.error(f"{self.log_prefix} 执行动作时出错: {e}")
@@ -570,11 +521,10 @@ class HeartFChatting:
"success": False,
"reply_text": "",
"loop_info": None,
"error": str(e),
"error": str(e)
}
# 创建所有动作的后台任务
# print(actions)
action_tasks = [asyncio.create_task(execute_action(action)) for action in actions]
@@ -588,11 +538,12 @@ class HeartFChatting:
action_reply_text = ""
action_command = ""
for result in results:
for i, result in enumerate(results):
if isinstance(result, BaseException):
logger.error(f"{self.log_prefix} 动作执行异常: {result}")
continue
action_info = actions[i]
if result["action_type"] != "reply":
action_success = result["success"]
action_reply_text = result["reply_text"]
@@ -632,6 +583,7 @@ class HeartFChatting:
}
reply_text = action_reply_text
if ENABLE_S4U:
await stop_typing()
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
@@ -722,61 +674,20 @@ class HeartFChatting:
# 处理动作并获取结果
result = await action_handler.handle_action()
success, reply_text = result
success, action_text = result
command = ""
if reply_text == "timeout":
self.reply_timeout_count += 1
if self.reply_timeout_count > 5:
logger.warning(
f"[{self.log_prefix} ] 连续回复超时次数过多,{global_config.chat.thinking_timeout}秒 内大模型没有返回有效内容请检查你的api是否速度过慢或配置错误。建议不要使用推理模型推理模型生成速度过慢。或者尝试拉高thinking_timeout参数这可能导致回复时间过长。"
)
logger.warning(f"{self.log_prefix} 回复生成超时{global_config.chat.thinking_timeout}s已跳过")
return False, "", ""
return success, reply_text, command
return success, action_text, command
except Exception as e:
logger.error(f"{self.log_prefix} 处理{action}时出错: {e}")
traceback.print_exc()
return False, "", ""
async def _generate_response(
self,
message_data: dict,
available_actions: Optional[Dict[str, ActionInfo]],
reply_to: str,
request_type: str = "chat.replyer.normal",
) -> Optional[list]:
"""生成普通回复"""
try:
success, reply_set, _ = await generator_api.generate_reply(
chat_stream=self.chat_stream,
reply_to=reply_to,
available_actions=available_actions,
enable_tool=global_config.tool.enable_tool,
request_type=request_type,
from_plugin=False,
)
if not success or not reply_set:
logger.info(f"{message_data.get('processed_plain_text')} 的回复生成失败")
return None
return reply_set
except Exception as e:
logger.error(f"{self.log_prefix}回复生成出现错误:{str(e)} {traceback.format_exc()}")
return None
async def _send_response(self, reply_set, reply_to, thinking_start_time, message_data) -> str:
current_time = time.time()
async def _send_response(self, reply_set, message_data) -> str:
new_message_count = message_api.count_new_messages(
chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time
chat_id=self.chat_stream.stream_id, start_time=self.last_read_time, end_time=time.time()
)
platform = message_data.get("user_platform", "")
user_id = message_data.get("user_id", "")
reply_to_platform_id = f"{platform}:{user_id}"
need_reply = new_message_count >= random.randint(2, 4)
@@ -788,27 +699,20 @@ class HeartFChatting:
for reply_seg in reply_set:
data = reply_seg[1]
if not first_replied:
if need_reply:
await send_api.text_to_stream(
text=data,
stream_id=self.chat_stream.stream_id,
reply_to=reply_to,
reply_to_platform_id=reply_to_platform_id,
typing=False,
)
else:
await send_api.text_to_stream(
text=data,
stream_id=self.chat_stream.stream_id,
reply_to_platform_id=reply_to_platform_id,
typing=False,
)
await send_api.text_to_stream(
text=data,
stream_id=self.chat_stream.stream_id,
reply_to_message = message_data,
set_reply=need_reply,
typing=False,
)
first_replied = True
else:
await send_api.text_to_stream(
text=data,
stream_id=self.chat_stream.stream_id,
reply_to_platform_id=reply_to_platform_id,
reply_to_message = message_data,
set_reply=False,
typing=True,
)
reply_text += data

View File

@@ -81,7 +81,7 @@ def init_prompt() -> None:
class ExpressionLearner:
def __init__(self, chat_id: str) -> None:
self.express_learn_model: LLMRequest = LLMRequest(
model_set=model_config.model_task_config.replyer_1, request_type="expressor.learner"
model_set=model_config.model_task_config.replyer, request_type="expressor.learner"
)
self.chat_id = chat_id
self.chat_name = get_chat_manager().get_stream_name(chat_id) or chat_id

View File

@@ -145,8 +145,6 @@ class HeartFCMessageReceiver:
# 3. 日志记录
mes_name = chat.group_info.group_name if chat.group_info else "私聊"
# current_time = time.strftime("%H:%M:%S", time.localtime(message.message_info.time))
current_talk_frequency = global_config.chat.get_current_talk_frequency(chat.stream_id)
# 如果消息中包含图片标识,则将 [picid:...] 替换为 [图片]
picid_pattern = r"\[picid:([^\]]+)\]"
@@ -164,7 +162,6 @@ class HeartFCMessageReceiver:
else:
logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}[兴趣度:{interested_rate:.2f}]") # type: ignore
logger.debug(f"[{mes_name}][当前时段回复频率: {current_talk_frequency}]")
# 4. 关系处理
if global_config.relationship.enable_relationship:

View File

@@ -42,9 +42,21 @@ def init_prompt():
{actions_before_now_block}
{no_action_block}
动作reply
动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附
- {mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应
{{
"action": "reply",
"target_message_id":"想要回复的消息id",
"reason":"回复的原因"
}}
{action_options_text}
你必须从上面列出的可用action中选择一个并说明触发action的消息id不是消息原文和选择该action的原因。
你必须从上面列出的可用action中选择一个并说明触发action的消息id不是消息原文和选择该action的原因。消息id格式:m+数字
请根据动作示例,以严格的 JSON 格式输出,且仅包含 JSON 内容:
""",
@@ -82,7 +94,6 @@ class ActionPlanner:
self.max_plan_retries = 3
def find_message_by_id(self, message_id: str, message_id_list: list) -> Optional[Dict[str, Any]]:
# sourcery skip: use-next
"""
根据message_id从message_id_list中查找对应的原始消息
@@ -180,19 +191,18 @@ class ActionPlanner:
parsed_json = {}
action = parsed_json.get("action", "no_reply")
reasoning = parsed_json.get("reasoning", "未提供原因")
reasoning = parsed_json.get("reason", "未提供原因")
# 将所有其他属性添加到action_data
for key, value in parsed_json.items():
if key not in ["action", "reasoning"]:
action_data[key] = value
# 在FOCUS模式下非no_reply动作需要target_message_id
# 非no_reply动作需要target_message_id
if action != "no_reply":
if target_message_id := parsed_json.get("target_message_id"):
# 根据target_message_id查找原始消息
target_message = self.find_message_by_id(target_message_id, message_id_list)
# target_message = None
# 如果获取的target_message为None输出warning并重新plan
if target_message is None:
self.plan_retry_count += 1
@@ -205,7 +215,7 @@ class ActionPlanner:
self.plan_retry_count = 0 # 重置计数器
else:
# 递归重新plan
return await self.plan(mode)
return await self.plan(mode, loop_start_time, available_actions)
else:
# 成功获取到target_message重置计数器
self.plan_retry_count = 0
@@ -213,9 +223,8 @@ class ActionPlanner:
logger.warning(f"{self.log_prefix}动作'{action}'缺少target_message_id")
if action == "no_action":
reasoning = "normal决定不使用额外动作"
elif action != "no_reply" and action != "reply" and action not in current_available_actions:
if action != "no_reply" and action != "reply" and action not in current_available_actions:
logger.warning(
f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'"
)
@@ -301,7 +310,6 @@ class ActionPlanner:
actions_before_now_block = f"你刚刚选择并执行过的action是\n{actions_before_now_block}"
if refresh_time:
self.last_obs_time_mark = time.time()
# logger.info(f"{self.log_prefix}当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
mentioned_bonus = ""
if global_config.chat.mentioned_bot_inevitable_reply:
@@ -311,43 +319,27 @@ class ActionPlanner:
if mode == ChatMode.FOCUS:
no_action_block = f"""重要说明:
- 'no_reply' 表示只进行不进行回复,等待合适的回复时机
no_action_block = """
- 'no_reply' 表示不进行回复,等待合适的回复时机
- 当你刚刚发送了消息没有人回复时选择no_reply
- 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply
动作no_reply
动作描述:不进行回复,等待合适的回复时机
- 当你刚刚发送了消息没有人回复时选择no_reply
- 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply
动作reply
动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附
- {mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应
- 不要回复自己发送的消息
{{
"action": "reply",
"target_message_id":"触发action的消息id",
"reason":"回复的原因"
"action": "no_reply",
"reason":"不回复的原因"
}}
"""
else:
no_action_block = f"""重要说明:
no_action_block = """重要说明:
- 'reply' 表示只进行普通聊天回复,不执行任何额外动作
- 其他action表示在普通回复的基础上执行相应的额外动作
动作reply
动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附
- {mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应
- 不要回复自己发送的消息
{{
"action": "reply",
"target_message_id":"触发action的消息id",
"reason":"回复的原因"
}}"""
"""
chat_context_description = "你现在正在一个群聊中"
chat_target_name = None # Only relevant for private
chat_target_name = None
if not is_group_chat and chat_target_info:
chat_target_name = (
chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or "对方"
@@ -399,6 +391,7 @@ class ActionPlanner:
chat_content_block=chat_content_block,
actions_before_now_block=actions_before_now_block,
no_action_block=no_action_block,
mentioned_bonus=mentioned_bonus,
action_options_text=action_options_block,
moderation_prompt=moderation_prompt_block,
identity_block=identity_block,

View File

@@ -97,9 +97,40 @@ def init_prompt():
不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。只输出一条回复内容就好
现在,你说:
""",
"s4u_style_prompt",
"replyer_prompt",
)
Prompt(
"""
{expression_habits_block}
{tool_info_block}
{knowledge_prompt}
{memory_block}
{relation_info_block}
{extra_info_block}
{identity}
{action_descriptions}
{time_block}
你现在正在一个QQ群里聊天以下是正在进行的聊天内容
{background_dialogue_prompt}
你现在想补充说明你刚刚自己的发言内容:{target}
请你根据聊天内容,组织一条新回复。
你现在的心情是:{mood_state}
{reply_style}
{keywords_reaction_prompt}
请注意不要输出多余内容(包括前后缀冒号和引号at或 @等 )。只输出回复内容。
{moderation_prompt}
不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。只输出一条回复内容就好
现在,你说:
""",
"replyer_self_prompt",
)
Prompt(
"""
你是一个专门获取知识的助手。你的名字是{bot_name}。现在是{time_now}
@@ -121,40 +152,11 @@ class DefaultReplyer:
def __init__(
self,
chat_stream: ChatStream,
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
request_type: str = "focus.replyer",
request_type: str = "replyer",
):
self.request_type = request_type
if model_set_with_weight:
# self.express_model_configs = model_configs
self.model_set: List[Tuple[TaskConfig, float]] = model_set_with_weight
else:
# 当未提供配置时,使用默认配置并赋予默认权重
# model_config_1 = global_config.model.replyer_1.copy()
# model_config_2 = global_config.model.replyer_2.copy()
prob_first = global_config.chat.replyer_random_probability
# model_config_1["weight"] = prob_first
# model_config_2["weight"] = 1.0 - prob_first
# self.express_model_configs = [model_config_1, model_config_2]
self.model_set = [
(model_config.model_task_config.replyer_1, prob_first),
(model_config.model_task_config.replyer_2, 1.0 - prob_first),
]
# if not self.express_model_configs:
# logger.warning("未找到有效的模型配置,回复生成可能会失败。")
# # 提供一个最终的回退,以防止在空列表上调用 random.choice
# fallback_config = global_config.model.replyer_1.copy()
# fallback_config.setdefault("weight", 1.0)
# self.express_model_configs = [fallback_config]
self.express_model = LLMRequest(model_set=model_config.model_task_config.replyer, request_type=request_type)
self.chat_stream = chat_stream
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_stream.stream_id)
self.heart_fc_sender = HeartFCSender()
self.memory_activator = MemoryActivator()
self.instant_memory = InstantMemory(chat_id=self.chat_stream.stream_id)
@@ -163,30 +165,23 @@ class DefaultReplyer:
self.tool_executor = ToolExecutor(chat_id=self.chat_stream.stream_id, enable_cache=True, cache_ttl=3)
def _select_weighted_models_config(self) -> Tuple[TaskConfig, float]:
"""使用加权随机选择来挑选一个模型配置"""
configs = self.model_set
# 提取权重,如果模型配置中没有'weight'键则默认为1.0
weights = [weight for _, weight in configs]
return random.choices(population=configs, weights=weights, k=1)[0]
async def generate_reply_with_context(
self,
reply_to: str = "",
extra_info: str = "",
reply_reason: str = "",
available_actions: Optional[Dict[str, ActionInfo]] = None,
enable_tool: bool = True,
from_plugin: bool = True,
stream_id: Optional[str] = None,
reply_message: Optional[Dict[str, Any]] = None,
) -> Tuple[bool, Optional[Dict[str, Any]], Optional[str]]:
# sourcery skip: merge-nested-ifs
"""
回复器 (Replier): 负责生成回复文本的核心逻辑。
Args:
reply_to: 回复对象,格式为 "发送者:消息内容"
extra_info: 额外信息,用于补充上下文
reply_reason: 回复原因
available_actions: 可用的动作信息字典
enable_tool: 是否启用工具调用
from_plugin: 是否来自插件
@@ -201,10 +196,11 @@ class DefaultReplyer:
# 3. 构建 Prompt
with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await self.build_prompt_reply_context(
reply_to=reply_to,
extra_info=extra_info,
available_actions=available_actions,
enable_tool=enable_tool,
reply_message=reply_message,
reply_reason=reply_reason,
)
if not prompt:
@@ -302,16 +298,11 @@ class DefaultReplyer:
traceback.print_exc()
return False, None, prompt if return_prompt else None
async def build_relation_info(self, reply_to: str = ""):
async def build_relation_info(self, sender: str, target: str):
if not global_config.relationship.enable_relationship:
return ""
relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id)
if not reply_to:
return ""
sender, text = self._parse_reply_target(reply_to)
if not sender or not text:
return ""
# 获取用户ID
person_info_manager = get_person_info_manager()
@@ -418,7 +409,7 @@ class DefaultReplyer:
return memory_str
async def build_tool_info(self, chat_history: str, reply_to: str = "", enable_tool: bool = True) -> str:
async def build_tool_info(self, chat_history: str, sender: str, target: str, enable_tool: bool = True) -> str:
"""构建工具信息块
Args:
@@ -433,18 +424,11 @@ class DefaultReplyer:
if not enable_tool:
return ""
if not reply_to:
return ""
sender, text = self._parse_reply_target(reply_to)
if not text:
return ""
try:
# 使用工具执行器获取信息
tool_results, _, _ = await self.tool_executor.execute_from_chat_message(
sender=sender, target_message=text, chat_history=chat_history, return_details=False
sender=sender, target_message=target, chat_history=chat_history, return_details=False
)
if tool_results:
@@ -668,21 +652,22 @@ class DefaultReplyer:
async def build_prompt_reply_context(
self,
reply_to: str,
extra_info: str = "",
reply_reason: str = "",
available_actions: Optional[Dict[str, ActionInfo]] = None,
enable_tool: bool = True,
) -> str: # sourcery skip: merge-else-if-into-elif, remove-redundant-if
reply_message: Optional[Dict[str, Any]] = None,
) -> str:
"""
构建回复器上下文
Args:
reply_to: 回复对象,格式为 "发送者:消息内容"
extra_info: 额外信息,用于补充上下文
reply_reason: 回复原因
available_actions: 可用动作
enable_timeout: 是否启用超时处理
enable_tool: 是否启用工具调用
reply_message: 回复的原始消息
Returns:
str: 构建好的上下文
"""
@@ -692,6 +677,20 @@ class DefaultReplyer:
chat_id = chat_stream.stream_id
person_info_manager = get_person_info_manager()
is_group_chat = bool(chat_stream.group_info)
platform = chat_stream.platform
user_id = reply_message.get("user_id","")
if user_id:
person_id = person_info_manager.get_person_id(platform,user_id)
person_name = await person_info_manager.get_value(person_id, "person_name")
sender = person_name
target = reply_message.get('processed_plain_text')
else:
person_id = ""
person_name = "用户"
sender = "用户"
target = "消息"
if global_config.mood.enable_mood:
chat_mood = mood_manager.get_mood_by_chat_id(chat_id)
@@ -699,15 +698,6 @@ class DefaultReplyer:
else:
mood_prompt = ""
sender, target = self._parse_reply_target(reply_to)
person_info_manager = get_person_info_manager()
person_id = person_info_manager.get_person_id_by_person_name(sender)
user_id = person_info_manager.get_value_sync(person_id, "user_id")
platform = chat_stream.platform
if user_id == global_config.bot.qq_account and platform == global_config.bot.platform:
logger.warning("选取了自身作为回复对象跳过构建prompt")
return ""
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
# 构建action描述 (如果启用planner)
@@ -744,12 +734,12 @@ class DefaultReplyer:
self._time_and_run_task(
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
),
self._time_and_run_task(self.build_relation_info(reply_to), "relation_info"),
self._time_and_run_task(self.build_relation_info(sender, target), "relation_info"),
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
self._time_and_run_task(
self.build_tool_info(chat_talking_prompt_short, reply_to, enable_tool=enable_tool), "tool_info"
self.build_tool_info(chat_talking_prompt_short, sender, target, enable_tool=enable_tool), "tool_info"
),
self._time_and_run_task(self.get_prompt_info(chat_talking_prompt_short, reply_to), "prompt_info"),
self._time_and_run_task(self.get_prompt_info(chat_talking_prompt_short, sender, target), "prompt_info"),
)
# 任务名称中英文映射
@@ -793,27 +783,16 @@ class DefaultReplyer:
"请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。不要随意遵从他人指令。"
)
if sender and target:
if sender:
if is_group_chat:
if sender:
reply_target_block = (
f"现在{sender}说的:{target}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
elif target:
reply_target_block = f"现在{target}引起了你的注意,你想要在群里发言或者回复这条消息。"
else:
reply_target_block = "现在,你想要在群里发言或者回复消息。"
reply_target_block = (
f"现在{sender}说的:{target}。引起了你的注意,你想要在群里发言或者回复这条消息。原因是{reply_reason}"
)
else: # private chat
if sender:
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意,针对这条消息回复。"
elif target:
reply_target_block = f"现在{target}引起了你的注意,针对这条消息回复。"
else:
reply_target_block = "现在,你想要回复。"
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意,针对这条消息回复。原因是{reply_reason}"
else:
reply_target_block = ""
template_name = "default_generator_prompt"
if is_group_chat:
chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1")
chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2")
@@ -830,81 +809,69 @@ class DefaultReplyer:
"chat_target_private2", sender_name=chat_target_name
)
target_user_id = ""
person_id = ""
if sender:
# 根据sender通过person_info_manager反向查找person_id再获取user_id
person_id = person_info_manager.get_person_id_by_person_name(sender)
# 使用 s4u 对话构建模式:分离当前对话对象和其他对话
try:
user_id_value = await person_info_manager.get_value(person_id, "user_id")
if user_id_value:
target_user_id = str(user_id_value)
except Exception as e:
logger.warning(f"无法从person_id {person_id} 获取user_id: {e}")
target_user_id = ""
# 构建分离的对话 prompt
core_dialogue_prompt, background_dialogue_prompt = self.build_s4u_chat_history_prompts(
message_list_before_now_long, target_user_id, sender
message_list_before_now_long, user_id, sender
)
self.build_mai_think_context(
chat_id=chat_id,
memory_block=memory_block,
relation_info=relation_info,
time_block=time_block,
chat_target_1=chat_target_1,
chat_target_2=chat_target_2,
mood_prompt=mood_prompt,
identity_block=identity_block,
sender=sender,
target=target,
chat_info=f"""
{background_dialogue_prompt}
--------------------------------
{time_block}
这是你和{sender}的对话,你们正在交流中:
{core_dialogue_prompt}""",
)
# 使用 s4u 风格的模板
template_name = "s4u_style_prompt"
return await global_prompt_manager.format_prompt(
template_name,
expression_habits_block=expression_habits_block,
tool_info_block=tool_info,
knowledge_prompt=prompt_info,
memory_block=memory_block,
relation_info_block=relation_info,
extra_info_block=extra_info_block,
identity=identity_block,
action_descriptions=action_descriptions,
sender_name=sender,
mood_state=mood_prompt,
background_dialogue_prompt=background_dialogue_prompt,
time_block=time_block,
core_dialogue_prompt=core_dialogue_prompt,
reply_target_block=reply_target_block,
message_txt=target,
reply_style=global_config.personality.reply_style,
keywords_reaction_prompt=keywords_reaction_prompt,
moderation_prompt=moderation_prompt_block,
)
if global_config.bot.qq_account == user_id and platform == global_config.bot.platform:
return await global_prompt_manager.format_prompt(
"replyer_self_prompt",
expression_habits_block=expression_habits_block,
tool_info_block=tool_info,
knowledge_prompt=prompt_info,
memory_block=memory_block,
relation_info_block=relation_info,
extra_info_block=extra_info_block,
identity=identity_block,
action_descriptions=action_descriptions,
mood_state=mood_prompt,
background_dialogue_prompt=background_dialogue_prompt,
time_block=time_block,
target = target,
reply_style=global_config.personality.reply_style,
keywords_reaction_prompt=keywords_reaction_prompt,
moderation_prompt=moderation_prompt_block,
)
else:
return await global_prompt_manager.format_prompt(
"replyer_prompt",
expression_habits_block=expression_habits_block,
tool_info_block=tool_info,
knowledge_prompt=prompt_info,
memory_block=memory_block,
relation_info_block=relation_info,
extra_info_block=extra_info_block,
identity=identity_block,
action_descriptions=action_descriptions,
sender_name=sender,
mood_state=mood_prompt,
background_dialogue_prompt=background_dialogue_prompt,
time_block=time_block,
core_dialogue_prompt=core_dialogue_prompt,
reply_target_block=reply_target_block,
reply_style=global_config.personality.reply_style,
keywords_reaction_prompt=keywords_reaction_prompt,
moderation_prompt=moderation_prompt_block,
)
async def build_prompt_rewrite_context(
self,
raw_reply: str,
reason: str,
reply_to: str,
reply_message: Optional[Dict[str, Any]] = None,
) -> str: # sourcery skip: merge-else-if-into-elif, remove-redundant-if
chat_stream = self.chat_stream
chat_id = chat_stream.stream_id
is_group_chat = bool(chat_stream.group_info)
sender, target = self._parse_reply_target(reply_to)
if reply_message:
sender = reply_message.get("sender")
target = reply_message.get("target")
else:
sender, target = self._parse_reply_target(reply_to)
# 添加情绪状态获取
if global_config.mood.enable_mood:
@@ -930,7 +897,7 @@ class DefaultReplyer:
# 并行执行2个构建任务
expression_habits_block, relation_info = await asyncio.gather(
self.build_expression_habits(chat_talking_prompt_half, target),
self.build_relation_info(reply_to),
self.build_relation_info(sender, target),
)
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
@@ -1035,34 +1002,25 @@ class DefaultReplyer:
async def llm_generate_content(self, prompt: str):
with Timer("LLM生成", {}): # 内部计时器,可选保留
# 加权随机选择一个模型配置
selected_model_config, weight = self._select_weighted_models_config()
logger.info(f"使用模型集生成回复: {selected_model_config} (选中概率: {weight})")
express_model = LLMRequest(model_set=selected_model_config, request_type=self.request_type)
# 直接使用已初始化的模型实例
logger.info(f"使用模型集生成回复: {self.express_model.model_for_task}")
if global_config.debug.show_prompt:
logger.info(f"\n{prompt}\n")
else:
logger.debug(f"\n{prompt}\n")
content, (reasoning_content, model_name, tool_calls) = await express_model.generate_response_async(prompt)
content, (reasoning_content, model_name, tool_calls) = await self.express_model.generate_response_async(prompt)
logger.debug(f"replyer生成内容: {content}")
return content, reasoning_content, model_name, tool_calls
async def get_prompt_info(self, message: str, reply_to: str):
async def get_prompt_info(self, message: str, sender: str, target: str):
related_info = ""
start_time = time.time()
from src.plugins.built_in.knowledge.lpmm_get_knowledge import SearchKnowledgeFromLPMMTool
if not reply_to:
logger.debug("没有回复对象,跳过获取知识库内容")
return ""
sender, content = self._parse_reply_target(reply_to)
if not content:
logger.debug("回复对象内容为空,跳过获取知识库内容")
return ""
logger.debug(f"获取知识库内容,元消息:{message[:30]}...,消息长度: {len(message)}")
# 从LPMM知识库获取知识
try:
@@ -1080,7 +1038,7 @@ class DefaultReplyer:
time_now=time_now,
chat_history=message,
sender=sender,
target_message=content,
target_message=target,
)
_, _, _, _, tool_calls = await llm_api.generate_with_model_with_tools(
prompt,

View File

@@ -16,7 +16,6 @@ class ReplyerManager:
self,
chat_stream: Optional[ChatStream] = None,
chat_id: Optional[str] = None,
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
request_type: str = "replyer",
) -> Optional[DefaultReplyer]:
"""
@@ -50,7 +49,6 @@ class ReplyerManager:
# model_configs 只在此时(初始化时)生效
replyer = DefaultReplyer(
chat_stream=target_stream,
model_set_with_weight=model_set_with_weight, # 可以是None此时使用默认模型
request_type=request_type,
)
self._repliers[stream_id] = replyer

View File

@@ -1,60 +0,0 @@
import asyncio
from src.config.config import global_config
from .willing_manager import BaseWillingManager
class ClassicalWillingManager(BaseWillingManager):
def __init__(self):
super().__init__()
self._decay_task: asyncio.Task | None = None
async def _decay_reply_willing(self):
"""定期衰减回复意愿"""
while True:
await asyncio.sleep(1)
for chat_id in self.chat_reply_willing:
self.chat_reply_willing[chat_id] = max(0.0, self.chat_reply_willing[chat_id] * 0.9)
async def async_task_starter(self):
if self._decay_task is None:
self._decay_task = asyncio.create_task(self._decay_reply_willing())
async def get_reply_probability(self, message_id):
willing_info = self.ongoing_messages[message_id]
chat_id = willing_info.chat_id
current_willing = self.chat_reply_willing.get(chat_id, 0)
# print(f"[{chat_id}] 回复意愿: {current_willing}")
interested_rate = willing_info.interested_rate
# print(f"[{chat_id}] 兴趣值: {interested_rate}")
current_willing += interested_rate
if willing_info.is_mentioned_bot and global_config.chat.mentioned_bot_inevitable_reply and current_willing < 2:
current_willing += 1 if current_willing < 1.0 else 0.2
self.chat_reply_willing[chat_id] = min(current_willing, 1.0)
reply_probability = min(max((current_willing - 0.5), 0.01) * 2, 1.5)
# print(f"[{chat_id}] 回复概率: {reply_probability}")
return reply_probability
async def before_generate_reply_handle(self, message_id):
pass
async def after_generate_reply_handle(self, message_id):
if message_id not in self.ongoing_messages:
return
chat_id = self.ongoing_messages[message_id].chat_id
current_willing = self.chat_reply_willing.get(chat_id, 0)
if current_willing < 1:
self.chat_reply_willing[chat_id] = min(1.0, current_willing + 0.3)
async def not_reply_handle(self, message_id):
return await super().not_reply_handle(message_id)

View File

@@ -1,23 +0,0 @@
from .willing_manager import BaseWillingManager
NOT_IMPLEMENTED_MESSAGE = "\ncustom模式你实现了吗没自行实现不要选custom。给你退了快点给你麦爹配置\n以上内容由gemini生成如有不满请投诉gemini"
class CustomWillingManager(BaseWillingManager):
async def async_task_starter(self) -> None:
raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
async def before_generate_reply_handle(self, message_id: str):
raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
async def after_generate_reply_handle(self, message_id: str):
raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
async def not_reply_handle(self, message_id: str):
raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
async def get_reply_probability(self, message_id: str):
raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)
def __init__(self):
super().__init__()
raise NotImplementedError(NOT_IMPLEMENTED_MESSAGE)

View File

@@ -1,296 +0,0 @@
"""
Mxp 模式:梦溪畔独家赞助
此模式的一些参数不会在配置文件中显示,要修改请在可变参数下修改
同时一些全局设置对此模式无效
此模式的可变参数暂时比较草率,需要调参仙人的大手
此模式的特点:
1.每个聊天流的每个用户的意愿是独立的
2.接入关系系统,关系会影响意愿值(已移除,因为关系系统重构)
3.会根据群聊的热度来调整基础意愿值
4.限制同时思考的消息数量,防止喷射
5.拥有单聊增益无论在群里还是私聊只要bot一直和你聊就会增加意愿值
6.意愿分为衰减意愿+临时意愿
7.疲劳机制
如果你发现本模式出现了bug
上上策是询问智慧的小草神()
上策是询问万能的千石可乐
中策是发issue
下下策是询问一个菜鸟(@梦溪畔)
"""
from typing import Dict
import asyncio
import time
import math
from src.chat.message_receive.chat_stream import ChatStream
from .willing_manager import BaseWillingManager
class MxpWillingManager(BaseWillingManager):
"""Mxp意愿管理器"""
def __init__(self):
super().__init__()
self.chat_person_reply_willing: Dict[str, Dict[str, float]] = {} # chat_id: {person_id: 意愿值}
self.chat_new_message_time: Dict[str, list[float]] = {} # 聊天流ID: 消息时间
self.last_response_person: Dict[str, tuple[str, int]] = {} # 上次回复的用户信息
self.temporary_willing: float = 0 # 临时意愿值
self.chat_bot_message_time: Dict[str, list[float]] = {} # 聊天流ID: bot已回复消息时间
self.chat_fatigue_punishment_list: Dict[
str, list[tuple[float, float]]
] = {} # 聊天流疲劳惩罚列, 聊天流ID: 惩罚时间列(开始时间,持续时间)
self.chat_fatigue_willing_attenuation: Dict[str, float] = {} # 聊天流疲劳意愿衰减值
# 可变参数
self.intention_decay_rate = 0.93 # 意愿衰减率
self.number_of_message_storage = 12 # 消息存储数量
self.expected_replies_per_min = 3 # 每分钟预期回复数
self.basic_maximum_willing = 0.5 # 基础最大意愿值
self.mention_willing_gain = 0.6 # 提及意愿增益
self.interest_willing_gain = 0.3 # 兴趣意愿增益
self.single_chat_gain = 0.12 # 单聊增益
self.fatigue_messages_triggered_num = self.expected_replies_per_min # 疲劳消息触发数量(int)
self.fatigue_coefficient = 1.0 # 疲劳系数
self.is_debug = False # 是否开启调试模式
async def async_task_starter(self) -> None:
"""异步任务启动器"""
asyncio.create_task(self._return_to_basic_willing())
asyncio.create_task(self._chat_new_message_to_change_basic_willing())
asyncio.create_task(self._fatigue_attenuation())
async def before_generate_reply_handle(self, message_id: str):
"""回复前处理"""
current_time = time.time()
async with self.lock:
w_info = self.ongoing_messages[message_id]
if w_info.chat_id not in self.chat_bot_message_time:
self.chat_bot_message_time[w_info.chat_id] = []
self.chat_bot_message_time[w_info.chat_id] = [
t for t in self.chat_bot_message_time[w_info.chat_id] if current_time - t < 60
]
self.chat_bot_message_time[w_info.chat_id].append(current_time)
if len(self.chat_bot_message_time[w_info.chat_id]) == int(self.fatigue_messages_triggered_num):
time_interval = 60 - (current_time - self.chat_bot_message_time[w_info.chat_id].pop(0))
self.chat_fatigue_punishment_list[w_info.chat_id].append((current_time, time_interval * 2))
async def after_generate_reply_handle(self, message_id: str):
"""回复后处理"""
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
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:
if now_chat_new_person[1] < 3:
tmp_list = list(now_chat_new_person)
tmp_list[1] += 1 # type: ignore
self.last_response_person[w_info.chat_id] = tuple(tmp_list) # type: ignore
else:
self.last_response_person[w_info.chat_id] = (w_info.person_id, 0)
async def not_reply_handle(self, message_id: str):
"""不回复处理"""
async with self.lock:
w_info = self.ongoing_messages[message_id]
if w_info.is_mentioned_bot:
self.chat_person_reply_willing[w_info.chat_id][w_info.person_id] += self.mention_willing_gain / 2.5
if (
w_info.chat_id in self.last_response_person
and self.last_response_person[w_info.chat_id][0] == w_info.person_id
and self.last_response_person[w_info.chat_id][1]
):
self.chat_person_reply_willing[w_info.chat_id][w_info.person_id] += self.single_chat_gain * (
2 * self.last_response_person[w_info.chat_id][1] - 1
)
now_chat_new_person = self.last_response_person.get(w_info.chat_id, ("", 0))
if now_chat_new_person[0] != w_info.person_id:
self.last_response_person[w_info.chat_id] = (w_info.person_id, 0)
async def get_reply_probability(self, message_id: str):
# sourcery skip: merge-duplicate-blocks, remove-redundant-if
"""获取回复概率"""
async with self.lock:
w_info = self.ongoing_messages[message_id]
current_willing = self.chat_person_reply_willing[w_info.chat_id][w_info.person_id]
if self.is_debug:
self.logger.debug(f"基础意愿值:{current_willing}")
if w_info.is_mentioned_bot:
willing_gain = self.mention_willing_gain / (int(current_willing) + 1)
current_willing += willing_gain
if self.is_debug:
self.logger.debug(f"提及增益:{willing_gain}")
if w_info.interested_rate > 0:
willing_gain = math.atan(w_info.interested_rate / 2) / math.pi * 2 * self.interest_willing_gain
current_willing += willing_gain
if self.is_debug:
self.logger.debug(f"兴趣增益:{willing_gain}")
self.chat_person_reply_willing[w_info.chat_id][w_info.person_id] = current_willing
# 添加单聊增益
if (
w_info.chat_id in self.last_response_person
and self.last_response_person[w_info.chat_id][0] == w_info.person_id
and self.last_response_person[w_info.chat_id][1]
):
current_willing += self.single_chat_gain * (2 * self.last_response_person[w_info.chat_id][1] + 1)
if self.is_debug:
self.logger.debug(
f"单聊增益:{self.single_chat_gain * (2 * self.last_response_person[w_info.chat_id][1] + 1)}"
)
current_willing += self.chat_fatigue_willing_attenuation.get(w_info.chat_id, 0)
if self.is_debug:
self.logger.debug(f"疲劳衰减:{self.chat_fatigue_willing_attenuation.get(w_info.chat_id, 0)}")
chat_ongoing_messages = [msg for msg in self.ongoing_messages.values() if msg.chat_id == w_info.chat_id]
chat_person_ongoing_messages = [msg for msg in chat_ongoing_messages if msg.person_id == w_info.person_id]
if len(chat_person_ongoing_messages) >= 2:
current_willing = 0
if self.is_debug:
self.logger.debug("进行中消息惩罚归0")
elif len(chat_ongoing_messages) == 2:
current_willing -= 0.5
if self.is_debug:
self.logger.debug("进行中消息惩罚:-0.5")
elif len(chat_ongoing_messages) == 3:
current_willing -= 1.5
if self.is_debug:
self.logger.debug("进行中消息惩罚:-1.5")
elif len(chat_ongoing_messages) >= 4:
current_willing = 0
if self.is_debug:
self.logger.debug("进行中消息惩罚归0")
probability = self._willing_to_probability(current_willing)
self.temporary_willing = current_willing
return probability
async def _return_to_basic_willing(self):
"""使每个人的意愿恢复到chat基础意愿"""
while True:
await asyncio.sleep(3)
async with self.lock:
for chat_id, person_willing in self.chat_person_reply_willing.items():
for person_id, willing in person_willing.items():
if chat_id not in self.chat_reply_willing:
self.logger.debug(f"聊天流{chat_id}不存在,错误")
continue
basic_willing = self.chat_reply_willing[chat_id]
person_willing[person_id] = (
basic_willing + (willing - basic_willing) * self.intention_decay_rate
)
def setup(self, message: dict, chat_stream: ChatStream):
super().setup(message, chat_stream)
stream_id = chat_stream.stream_id
self.chat_reply_willing[stream_id] = self.chat_reply_willing.get(stream_id, self.basic_maximum_willing)
self.chat_person_reply_willing[stream_id] = self.chat_person_reply_willing.get(stream_id, {})
self.chat_person_reply_willing[stream_id][self.ongoing_messages[message.get("message_id", "")].person_id] = (
self.chat_person_reply_willing[stream_id].get(
self.ongoing_messages[message.get("message_id", "")].person_id,
self.chat_reply_willing[stream_id],
)
)
current_time = time.time()
if stream_id not in self.chat_new_message_time:
self.chat_new_message_time[stream_id] = []
self.chat_new_message_time[stream_id].append(current_time)
if len(self.chat_new_message_time[stream_id]) > self.number_of_message_storage:
self.chat_new_message_time[stream_id].pop(0)
if stream_id not in self.chat_fatigue_punishment_list:
self.chat_fatigue_punishment_list[stream_id] = [
(
current_time,
self.number_of_message_storage * self.basic_maximum_willing / self.expected_replies_per_min * 60,
)
]
self.chat_fatigue_willing_attenuation[stream_id] = (
-2 * self.basic_maximum_willing * self.fatigue_coefficient
)
@staticmethod
def _willing_to_probability(willing: float) -> float:
"""意愿值转化为概率"""
willing = max(0, willing)
if willing < 2:
return math.atan(willing * 2) / math.pi * 2
elif willing < 2.5:
return math.atan(willing * 4) / math.pi * 2
else:
return 1
async def _chat_new_message_to_change_basic_willing(self):
"""聊天流新消息改变基础意愿"""
update_time = 20
while True:
await asyncio.sleep(update_time)
async with self.lock:
for chat_id, message_times in self.chat_new_message_time.items():
# 清理过期消息
current_time = time.time()
message_times = [
msg_time
for msg_time in message_times
if current_time - msg_time
< self.number_of_message_storage
* self.basic_maximum_willing
/ self.expected_replies_per_min
* 60
]
self.chat_new_message_time[chat_id] = message_times
if len(message_times) < self.number_of_message_storage:
self.chat_reply_willing[chat_id] = self.basic_maximum_willing
update_time = 20
elif len(message_times) == self.number_of_message_storage:
time_interval = current_time - message_times[0]
basic_willing = self._basic_willing_calculate(time_interval)
self.chat_reply_willing[chat_id] = basic_willing
update_time = 17 * basic_willing / self.basic_maximum_willing + 3
else:
self.logger.debug(f"聊天流{chat_id}消息时间数量异常,数量:{len(message_times)}")
self.chat_reply_willing[chat_id] = 0
if self.is_debug:
self.logger.debug(f"聊天流意愿值更新:{self.chat_reply_willing}")
def _basic_willing_calculate(self, t: float) -> float:
"""基础意愿值计算"""
return math.tan(t * self.expected_replies_per_min * math.pi / 120 / self.number_of_message_storage) / 2
async def _fatigue_attenuation(self):
"""疲劳衰减"""
while True:
await asyncio.sleep(1)
current_time = time.time()
async with self.lock:
for chat_id, fatigue_list in self.chat_fatigue_punishment_list.items():
fatigue_list = [z for z in fatigue_list if current_time - z[0] < z[1]]
self.chat_fatigue_willing_attenuation[chat_id] = 0
for start_time, duration in fatigue_list:
self.chat_fatigue_willing_attenuation[chat_id] += (
self.chat_reply_willing[chat_id]
* 2
/ math.pi
* math.asin(2 * (current_time - start_time) / duration - 1)
- self.chat_reply_willing[chat_id]
) * self.fatigue_coefficient
async def get_willing(self, chat_id):
return self.temporary_willing

View File

@@ -1,180 +0,0 @@
import importlib
import asyncio
from abc import ABC, abstractmethod
from typing import Dict, Optional, Any
from rich.traceback import install
from dataclasses import dataclass
from src.common.logger import get_logger
from src.config.config import global_config
from src.chat.message_receive.chat_stream import ChatStream, GroupInfo
from src.person_info.person_info import PersonInfoManager, get_person_info_manager
install(extra_lines=3)
"""
基类方法概览:
以下8个方法是你必须在子类重写的哪怕什么都不干
async_task_starter 在程序启动时执行在其中用asyncio.create_task启动你想要执行的异步任务
before_generate_reply_handle 确定要回复后,在生成回复前的处理
after_generate_reply_handle 确定要回复后,在生成回复后的处理
not_reply_handle 确定不回复后的处理
get_reply_probability 获取回复概率
get_variable_parameters 暂不确定
set_variable_parameters 暂不确定
以下2个方法根据你的实现可以做调整
get_willing 获取某聊天流意愿
set_willing 设置某聊天流意愿
规范说明:
模块文件命名: `mode_{manager_type}.py`
示例: 若 `manager_type="aggressive"`,则模块文件应为 `mode_aggressive.py`
类命名: `{manager_type}WillingManager` (首字母大写)
示例: 在 `mode_aggressive.py` 中,类名应为 `AggressiveWillingManager`
"""
logger = get_logger("willing")
@dataclass
class WillingInfo:
"""此类保存意愿模块常用的参数
Attributes:
message (MessageRecv): 原始消息对象
chat (ChatStream): 聊天流对象
person_info_manager (PersonInfoManager): 用户信息管理对象
chat_id (str): 当前聊天流的标识符
person_id (str): 发送者的个人信息的标识符
group_id (str): 群组ID如果是私聊则为空
is_mentioned_bot (bool): 是否提及了bot
is_emoji (bool): 是否为表情包
interested_rate (float): 兴趣度
"""
message: Dict[str, Any] # 原始消息数据
chat: ChatStream
person_info_manager: PersonInfoManager
chat_id: str
person_id: str
group_info: Optional[GroupInfo]
is_mentioned_bot: bool
is_emoji: bool
is_picid: bool
interested_rate: float
# current_mood: float 当前心情?
class BaseWillingManager(ABC):
"""回复意愿管理基类"""
@classmethod
def create(cls, manager_type: str) -> "BaseWillingManager":
try:
module = importlib.import_module(f".mode_{manager_type}", __package__)
manager_class = getattr(module, f"{manager_type.capitalize()}WillingManager")
if not issubclass(manager_class, cls):
raise TypeError(f"Manager class {manager_class.__name__} is not a subclass of {cls.__name__}")
else:
logger.info(f"普通回复模式:{manager_type}")
return manager_class()
except (ImportError, AttributeError, TypeError) as e:
module = importlib.import_module(".mode_classical", __package__)
manager_class = module.ClassicalWillingManager
logger.info(f"载入当前意愿模式{manager_type}失败,使用经典配方~~~~")
logger.debug(f"加载willing模式{manager_type}失败,原因: {str(e)}")
return manager_class()
def __init__(self):
self.chat_reply_willing: Dict[str, float] = {} # 存储每个聊天流的回复意愿(chat_id)
self.ongoing_messages: Dict[str, WillingInfo] = {} # 当前正在进行的消息(message_id)
self.lock = asyncio.Lock()
self.logger = logger
def setup(self, message: dict, chat: ChatStream):
person_id = PersonInfoManager.get_person_id(chat.platform, chat.user_info.user_id) # type: ignore
self.ongoing_messages[message.get("message_id", "")] = WillingInfo(
message=message,
chat=chat,
person_info_manager=get_person_info_manager(),
chat_id=chat.stream_id,
person_id=person_id,
group_info=chat.group_info,
is_mentioned_bot=message.get("is_mentioned", False),
is_emoji=message.get("is_emoji", False),
is_picid=message.get("is_picid", False),
interested_rate = message.get("interest_value") or 0.0,
)
def delete(self, message_id: str):
del_message = self.ongoing_messages.pop(message_id, None)
if not del_message:
logger.debug(f"尝试删除不存在的消息 ID: {message_id},可能已被其他流程处理,喵~")
@abstractmethod
async def async_task_starter(self) -> None:
"""抽象方法:异步任务启动器"""
pass
@abstractmethod
async def before_generate_reply_handle(self, message_id: str):
"""抽象方法:回复前处理"""
pass
@abstractmethod
async def after_generate_reply_handle(self, message_id: str):
"""抽象方法:回复后处理"""
pass
@abstractmethod
async def not_reply_handle(self, message_id: str):
"""抽象方法:不回复处理"""
pass
@abstractmethod
async def get_reply_probability(self, message_id: str):
"""抽象方法:获取回复概率"""
raise NotImplementedError
async def get_willing(self, chat_id: str):
"""获取指定聊天流的回复意愿"""
async with self.lock:
return self.chat_reply_willing.get(chat_id, 0)
async def set_willing(self, chat_id: str, willing: float):
"""设置指定聊天流的回复意愿"""
async with self.lock:
self.chat_reply_willing[chat_id] = willing
# @abstractmethod
# async def get_variable_parameters(self) -> Dict[str, str]:
# """抽象方法:获取可变参数"""
# pass
# @abstractmethod
# async def set_variable_parameters(self, parameters: Dict[str, any]):
# """抽象方法:设置可变参数"""
# pass
def init_willing_manager() -> BaseWillingManager:
"""
根据配置初始化并返回对应的WillingManager实例
Returns:
对应mode的WillingManager实例
"""
mode = global_config.normal_chat.willing_mode.lower()
return BaseWillingManager.create(mode)
# 全局willing_manager对象
willing_manager = None
def get_willing_manager():
global willing_manager
if willing_manager is None:
willing_manager = init_willing_manager()
return willing_manager

View File

@@ -273,6 +273,29 @@ class PersonInfo(BaseModel):
table_name = "person_info"
class GroupInfo(BaseModel):
"""
用于存储群组信息数据的模型。
"""
group_id = TextField(unique=True, index=True) # 群组唯一ID
group_name = TextField(null=True) # 群组名称 (允许为空)
platform = TextField() # 平台
group_number = TextField(index=True) # 群号
group_impression = TextField(null=True) # 群组印象
short_impression = TextField(null=True) # 群组印象的简短描述
member_list = TextField(null=True) # 群成员列表 (JSON格式)
group_info = TextField(null=True) # 群组基本信息
create_time = FloatField(null=True) # 创建时间 (时间戳)
last_active = FloatField(null=True) # 最后活跃时间
member_count = IntegerField(null=True, default=0) # 成员数量
class Meta:
# database = db # 继承自 BaseModel
table_name = "group_info"
class Memory(BaseModel):
memory_id = TextField(index=True)
chat_id = TextField(null=True)

View File

@@ -99,12 +99,9 @@ class ModelTaskConfig(ConfigBase):
utils_small: TaskConfig
"""组件小模型配置"""
replyer_1: TaskConfig
replyer: TaskConfig
"""normal_chat首要回复模型模型配置"""
replyer_2: TaskConfig
"""normal_chat次要回复模型配置"""
emotion: TaskConfig
"""情绪模型配置"""

View File

@@ -17,7 +17,6 @@ from src.config.official_configs import (
PersonalityConfig,
ExpressionConfig,
ChatConfig,
NormalChatConfig,
EmojiConfig,
MemoryConfig,
MoodConfig,
@@ -331,7 +330,6 @@ class Config(ConfigBase):
relationship: RelationshipConfig
chat: ChatConfig
message_receive: MessageReceiveConfig
normal_chat: NormalChatConfig
emoji: EmojiConfig
expression: ExpressionConfig
memory: MemoryConfig

View File

@@ -72,28 +72,26 @@ class ChatConfig(ConfigBase):
max_context_size: int = 18
"""上下文长度"""
replyer_random_probability: float = 0.5
"""
发言时选择推理模型的概率0-1之间
选择普通模型的概率为 1 - reasoning_normal_model_probability
"""
thinking_timeout: int = 40
"""麦麦最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢"""
talk_frequency: float = 1
"""回复频率阈值"""
mentioned_bot_inevitable_reply: bool = False
"""提及 bot 必然回复"""
at_bot_inevitable_reply: bool = False
"""@bot 必然回复"""
talk_frequency: float = 0.5
"""回复频率阈值"""
# 合并后的时段频率配置
talk_frequency_adjust: list[list[str]] = field(default_factory=lambda: [])
focus_value: float = 0.5
"""麦麦的专注思考能力越低越容易专注消耗token也越多"""
focus_value_adjust: list[list[str]] = field(default_factory=lambda: [])
"""
统一的时段频率配置
统一的活跃度和专注度配置
格式:[["platform:chat_id:type", "HH:MM,frequency", "HH:MM,frequency", ...], ...]
全局配置示例:
@@ -110,11 +108,31 @@ class ChatConfig(ConfigBase):
- 当第一个元素为空字符串""时,表示全局默认配置
- 当第一个元素为"platform:id:type"格式时,表示特定聊天流配置
- 后续元素是"时间,频率"格式,表示从该时间开始使用该频率,直到下一个时间点
- 优先级:特定聊天流配置 > 全局配置 > 默认 talk_frequency
- 优先级:特定聊天流配置 > 全局配置 > 默认
注意:
- talk_frequency_adjust 控制回复频率,数值越高回复越频繁
- focus_value_adjust 控制专注思考能力数值越低越容易专注消耗token也越多
"""
focus_value: float = 1.0
"""麦麦的专注思考能力越低越容易专注消耗token也越多"""
def get_current_focus_value(self, chat_stream_id: Optional[str] = None) -> float:
"""
根据当前时间和聊天流获取对应的 focus_value
"""
if not self.focus_value_adjust:
return self.focus_value
if chat_stream_id:
stream_focus_value = self._get_stream_specific_focus_value(chat_stream_id)
if stream_focus_value is not None:
return stream_focus_value
global_focus_value = self._get_global_focus_value()
if global_focus_value is not None:
return global_focus_value
return self.focus_value
def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float:
"""
@@ -139,6 +157,71 @@ class ChatConfig(ConfigBase):
global_frequency = self._get_global_frequency()
return self.talk_frequency if global_frequency is None else global_frequency
def _get_global_focus_value(self) -> Optional[float]:
"""
获取全局默认专注度配置
Returns:
float: 专注度值,如果没有配置则返回 None
"""
for config_item in self.focus_value_adjust:
if not config_item or len(config_item) < 2:
continue
# 检查是否为全局默认配置(第一个元素为空字符串)
if config_item[0] == "":
return self._get_time_based_focus_value(config_item[1:])
return None
def _get_time_based_focus_value(self, time_focus_list: list[str]) -> Optional[float]:
"""
根据时间配置列表获取当前时段的专注度
Args:
time_focus_list: 时间专注度配置列表,格式为 ["HH:MM,focus_value", ...]
Returns:
float: 专注度值,如果没有配置则返回 None
"""
from datetime import datetime
current_time = datetime.now().strftime("%H:%M")
current_hour, current_minute = map(int, current_time.split(":"))
current_minutes = current_hour * 60 + current_minute
# 解析时间专注度配置
time_focus_pairs = []
for time_focus_str in time_focus_list:
try:
time_str, focus_str = time_focus_str.split(",")
hour, minute = map(int, time_str.split(":"))
focus_value = float(focus_str)
minutes = hour * 60 + minute
time_focus_pairs.append((minutes, focus_value))
except (ValueError, IndexError):
continue
if not time_focus_pairs:
return None
# 按时间排序
time_focus_pairs.sort(key=lambda x: x[0])
# 查找当前时间对应的专注度
current_focus_value = None
for minutes, focus_value in time_focus_pairs:
if current_minutes >= minutes:
current_focus_value = focus_value
else:
break
# 如果当前时间在所有配置时间之前,使用最后一个时间段的专注度(跨天逻辑)
if current_focus_value is None and time_focus_pairs:
current_focus_value = time_focus_pairs[-1][1]
return current_focus_value
def _get_time_based_frequency(self, time_freq_list: list[str]) -> Optional[float]:
"""
根据时间配置列表获取当前时段的频率
@@ -187,6 +270,37 @@ class ChatConfig(ConfigBase):
return current_frequency
def _get_stream_specific_focus_value(self, chat_stream_id: str) -> Optional[float]:
"""
获取特定聊天流在当前时间的专注度
Args:
chat_stream_id: 聊天流ID哈希值
Returns:
float: 专注度值,如果没有配置则返回 None
"""
# 查找匹配的聊天流配置
for config_item in self.focus_value_adjust:
if not config_item or len(config_item) < 2:
continue
stream_config_str = config_item[0] # 例如 "qq:1026294844:group"
# 解析配置字符串并生成对应的 chat_id
config_chat_id = self._parse_stream_config_to_chat_id(stream_config_str)
if config_chat_id is None:
continue
# 比较生成的 chat_id
if config_chat_id != chat_stream_id:
continue
# 使用通用的时间专注度解析方法
return self._get_time_based_focus_value(config_item[1:])
return None
def _get_stream_specific_frequency(self, chat_stream_id: str):
"""
获取特定聊天流在当前时间的频率
@@ -281,15 +395,6 @@ class MessageReceiveConfig(ConfigBase):
ban_msgs_regex: set[str] = field(default_factory=lambda: set())
"""过滤正则表达式列表"""
@dataclass
class NormalChatConfig(ConfigBase):
"""普通聊天配置类"""
willing_mode: str = "classical"
"""意愿模式"""
@dataclass
class ExpressionConfig(ConfigBase):
"""表达配置类"""

View File

@@ -6,7 +6,6 @@ from src.common.remote import TelemetryHeartBeatTask
from src.manager.async_task_manager import async_task_manager
from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask
from src.chat.emoji_system.emoji_manager import get_emoji_manager
from src.chat.willing.willing_manager import get_willing_manager
from src.chat.message_receive.chat_stream import get_chat_manager
from src.config.config import global_config
from src.chat.message_receive.bot import chat_bot
@@ -31,8 +30,6 @@ if global_config.memory.enable_memory:
install(extra_lines=3)
willing_manager = get_willing_manager()
logger = get_logger("main")
@@ -91,11 +88,6 @@ class MainSystem:
get_emoji_manager().initialize()
logger.info("表情包管理器初始化成功")
# 启动愿望管理器
await willing_manager.async_task_starter()
logger.info("willing管理器初始化成功")
# 启动情绪管理器
await mood_manager.start()
logger.info("情绪管理器初始化成功")

View File

@@ -60,7 +60,7 @@ class MaiThinking:
self.sender = ""
self.target = ""
self.thinking_model = LLMRequest(model_set=model_config.model_task_config.replyer_1, request_type="thinking")
self.thinking_model = LLMRequest(model_set=model_config.model_task_config.replyer, request_type="thinking")
async def do_think_before_response(self):
pass

View File

@@ -13,8 +13,8 @@ logger = get_logger("s4u_stream_generator")
class S4UStreamGenerator:
def __init__(self):
replyer_1_config = model_config.model_task_config.replyer_1
model_to_use = replyer_1_config.model_list[0]
replyer_config = model_config.model_task_config.replyer
model_to_use = replyer_config.model_list[0]
model_info = model_config.get_model_info(model_to_use)
if not model_info:
logger.error(f"模型 {model_to_use} 在配置中未找到")
@@ -22,8 +22,8 @@ class S4UStreamGenerator:
provider_name = model_info.api_provider
provider_info = model_config.get_provider(provider_name)
if not provider_info:
logger.error("`replyer_1` 找不到对应的Provider")
raise ValueError("`replyer_1` 找不到对应的Provider")
logger.error("`replyer` 找不到对应的Provider")
raise ValueError("`replyer` 找不到对应的Provider")
api_key = provider_info.api_key
base_url = provider_info.base_url
@@ -34,7 +34,7 @@ class S4UStreamGenerator:
self.client_1 = AsyncOpenAIClient(api_key=api_key, base_url=base_url)
self.model_1_name = model_to_use
self.replyer_1_config = replyer_1_config
self.replyer_config = replyer_config
self.current_model_name = "unknown model"
self.partial_response = ""
@@ -104,10 +104,10 @@ class S4UStreamGenerator:
self.current_model_name = self.model_1_name
extra_kwargs = {}
if self.replyer_1_config.get("enable_thinking") is not None:
extra_kwargs["enable_thinking"] = self.replyer_1_config.get("enable_thinking")
if self.replyer_1_config.get("thinking_budget") is not None:
extra_kwargs["thinking_budget"] = self.replyer_1_config.get("thinking_budget")
if self.replyer_config.get("enable_thinking") is not None:
extra_kwargs["enable_thinking"] = self.replyer_config.get("enable_thinking")
if self.replyer_config.get("thinking_budget") is not None:
extra_kwargs["thinking_budget"] = self.replyer_config.get("thinking_budget")
async for chunk in self._generate_response_with_model(
prompt, current_client, self.current_model_name, **extra_kwargs

View File

@@ -32,7 +32,6 @@ logger = get_logger("generator_api")
def get_replyer(
chat_stream: Optional[ChatStream] = None,
chat_id: Optional[str] = None,
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
request_type: str = "replyer",
) -> Optional[DefaultReplyer]:
"""获取回复器对象
@@ -43,7 +42,6 @@ def get_replyer(
Args:
chat_stream: 聊天流对象(优先)
chat_id: 聊天ID实际上就是stream_id
model_set_with_weight: 模型配置列表,每个元素为 (TaskConfig, weight) 元组
request_type: 请求类型
Returns:
@@ -59,7 +57,6 @@ def get_replyer(
return replyer_manager.get_replyer(
chat_stream=chat_stream,
chat_id=chat_id,
model_set_with_weight=model_set_with_weight,
request_type=request_type,
)
except Exception as e:
@@ -78,13 +75,14 @@ async def generate_reply(
chat_id: Optional[str] = None,
action_data: Optional[Dict[str, Any]] = None,
reply_to: str = "",
reply_message: Optional[Dict[str, Any]] = None,
extra_info: str = "",
reply_reason: str = "",
available_actions: Optional[Dict[str, ActionInfo]] = None,
enable_tool: bool = False,
enable_splitter: bool = True,
enable_chinese_typo: bool = True,
return_prompt: bool = False,
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
request_type: str = "generator_api",
from_plugin: bool = True,
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
@@ -95,7 +93,9 @@ async def generate_reply(
chat_id: 聊天ID备用
action_data: 动作数据向下兼容包含reply_to和extra_info
reply_to: 回复对象,格式为 "发送者:消息内容"
reply_message: 回复消息
extra_info: 额外信息,用于补充上下文
reply_reason: 回复原因
available_actions: 可用动作
enable_tool: 是否启用工具调用
enable_splitter: 是否启用消息分割器
@@ -110,7 +110,7 @@ async def generate_reply(
try:
# 获取回复器
replyer = get_replyer(
chat_stream, chat_id, model_set_with_weight=model_set_with_weight, request_type=request_type
chat_stream, chat_id, request_type=request_type
)
if not replyer:
logger.error("[GeneratorAPI] 无法获取回复器")
@@ -118,17 +118,22 @@ async def generate_reply(
logger.debug("[GeneratorAPI] 开始生成回复")
if not reply_to and action_data:
reply_to = action_data.get("reply_to", "")
if reply_to:
logger.warning("[GeneratorAPI] 在0.10.0, reply_to 参数已弃用,请使用 reply_message 参数")
if not extra_info and action_data:
extra_info = action_data.get("extra_info", "")
if not reply_reason and action_data:
reply_reason = action_data.get("reason", "")
# 调用回复器生成回复
success, llm_response_dict, prompt = await replyer.generate_reply_with_context(
reply_to=reply_to,
extra_info=extra_info,
available_actions=available_actions,
enable_tool=enable_tool,
reply_message=reply_message,
reply_reason=reply_reason,
from_plugin=from_plugin,
stream_id=chat_stream.stream_id if chat_stream else chat_id,
)
@@ -166,11 +171,11 @@ async def rewrite_reply(
chat_id: Optional[str] = None,
enable_splitter: bool = True,
enable_chinese_typo: bool = True,
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
raw_reply: str = "",
reason: str = "",
reply_to: str = "",
return_prompt: bool = False,
request_type: str = "generator_api",
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
"""重写回复
@@ -191,7 +196,7 @@ async def rewrite_reply(
"""
try:
# 获取回复器
replyer = get_replyer(chat_stream, chat_id, model_set_with_weight=model_set_with_weight)
replyer = get_replyer(chat_stream, chat_id, request_type=request_type)
if not replyer:
logger.error("[GeneratorAPI] 无法获取回复器")
return False, [], None
@@ -258,10 +263,10 @@ def process_human_text(content: str, enable_splitter: bool, enable_chinese_typo:
async def generate_response_custom(
chat_stream: Optional[ChatStream] = None,
chat_id: Optional[str] = None,
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
request_type: str = "generator_api",
prompt: str = "",
) -> Optional[str]:
replyer = get_replyer(chat_stream, chat_id, model_set_with_weight=model_set_with_weight)
replyer = get_replyer(chat_stream, chat_id, request_type=request_type)
if not replyer:
logger.error("[GeneratorAPI] 无法获取回复器")
return None

View File

@@ -22,7 +22,7 @@
import traceback
import time
import difflib
from typing import Optional, Union
from typing import Optional, Union, Dict, Any
from src.common.logger import get_logger
# 导入依赖
@@ -49,7 +49,8 @@ async def _send_to_target(
display_message: str = "",
typing: bool = False,
reply_to: str = "",
reply_to_platform_id: Optional[str] = None,
set_reply: bool = False,
reply_to_message: Optional[Dict[str, Any]] = None,
storage_message: bool = True,
show_log: bool = True,
) -> bool:
@@ -62,7 +63,6 @@ async def _send_to_target(
display_message: 显示消息
typing: 是否模拟打字等待。
reply_to: 回复消息,格式为"发送者:消息内容"
reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!)
storage_message: 是否存储消息到数据库
show_log: 发送是否显示日志
@@ -70,6 +70,9 @@ async def _send_to_target(
bool: 是否发送成功
"""
try:
if reply_to:
logger.warning("[SendAPI] 在0.10.0, reply_to 参数已弃用,请使用 reply_to_message 参数")
if show_log:
logger.debug(f"[SendAPI] 发送{message_type}消息到 {stream_id}")
@@ -96,14 +99,14 @@ async def _send_to_target(
# 创建消息段
message_segment = Seg(type=message_type, data=content) # type: ignore
# 处理回复消息
anchor_message = None
if reply_to:
anchor_message = await _find_reply_message(target_stream, reply_to)
if anchor_message and anchor_message.message_info.user_info and not reply_to_platform_id:
reply_to_platform_id = (
f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}"
)
if reply_to_message:
anchor_message = message_dict_to_message_recv(reply_to_message)
anchor_message.update_chat_stream(target_stream)
reply_to_platform_id = (
f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}"
)
else:
anchor_message = None
# 构建发送消息对象
bot_message = MessageSending(
@@ -124,7 +127,7 @@ async def _send_to_target(
sent_msg = await heart_fc_sender.send_message(
bot_message,
typing=typing,
set_reply=(anchor_message is not None),
set_reply=set_reply,
storage_message=storage_message,
show_log=show_log,
)
@@ -142,111 +145,56 @@ async def _send_to_target(
return False
async def _find_reply_message(target_stream, reply_to: str) -> Optional[MessageRecv]:
# sourcery skip: inline-variable, use-named-expression
def message_dict_to_message_recv(message_dict: Dict[str, Any]) -> Optional[MessageRecv]:
"""查找要回复的消息
Args:
target_stream: 目标聊天流
reply_to: 回复格式,如"发送者:消息内容""发送者:消息内容"
message_dict: 消息字典
Returns:
Optional[MessageRecv]: 找到的消息如果没找到则返回None
"""
try:
# 解析reply_to参数
if ":" in reply_to:
parts = reply_to.split(":", 1)
elif "" in reply_to:
parts = reply_to.split("", 1)
else:
logger.warning(f"[SendAPI] reply_to格式不正确: {reply_to}")
return None
# 构建MessageRecv对象
user_info = {
"platform": message_dict.get("user_platform", ""),
"user_id": message_dict.get("user_id", ""),
"user_nickname": message_dict.get("user_nickname", ""),
"user_cardname": message_dict.get("user_cardname", ""),
}
if len(parts) != 2:
logger.warning(f"[SendAPI] reply_to格式不正确: {reply_to}")
return None
sender = parts[0].strip()
text = parts[1].strip()
# 获取聊天流的最新20条消息
reverse_talking_message = get_raw_msg_before_timestamp_with_chat(
target_stream.stream_id,
time.time(), # 当前时间之前的消息
20, # 最新的20条消息
)
# 反转列表,使最新的消息在前面
reverse_talking_message = list(reversed(reverse_talking_message))
find_msg = None
for message in reverse_talking_message:
user_id = message["user_id"]
platform = message["chat_info_platform"]
person_id = get_person_info_manager().get_person_id(platform, user_id)
person_name = await get_person_info_manager().get_value(person_id, "person_name")
if person_name == sender:
translate_text = message["processed_plain_text"]
# 使用独立函数处理用户引用格式
translate_text = await replace_user_references_async(translate_text, platform)
similarity = difflib.SequenceMatcher(None, text, translate_text).ratio()
if similarity >= 0.9:
find_msg = message
break
if not find_msg:
logger.info("[SendAPI] 未找到匹配的回复消息")
return None
# 构建MessageRecv对象
user_info = {
"platform": find_msg.get("user_platform", ""),
"user_id": find_msg.get("user_id", ""),
"user_nickname": find_msg.get("user_nickname", ""),
"user_cardname": find_msg.get("user_cardname", ""),
group_info = {}
if message_dict.get("chat_info_group_id"):
group_info = {
"platform": message_dict.get("chat_info_group_platform", ""),
"group_id": message_dict.get("chat_info_group_id", ""),
"group_name": message_dict.get("chat_info_group_name", ""),
}
group_info = {}
if find_msg.get("chat_info_group_id"):
group_info = {
"platform": find_msg.get("chat_info_group_platform", ""),
"group_id": find_msg.get("chat_info_group_id", ""),
"group_name": find_msg.get("chat_info_group_name", ""),
}
format_info = {"content_format": "", "accept_format": ""}
template_info = {"template_items": {}}
format_info = {"content_format": "", "accept_format": ""}
template_info = {"template_items": {}}
message_info = {
"platform": message_dict.get("chat_info_platform", ""),
"message_id": message_dict.get("message_id"),
"time": message_dict.get("time"),
"group_info": group_info,
"user_info": user_info,
"additional_config": message_dict.get("additional_config"),
"format_info": format_info,
"template_info": template_info,
}
message_info = {
"platform": target_stream.platform,
"message_id": find_msg.get("message_id"),
"time": find_msg.get("time"),
"group_info": group_info,
"user_info": user_info,
"additional_config": find_msg.get("additional_config"),
"format_info": format_info,
"template_info": template_info,
}
message_dict = {
"message_info": message_info,
"raw_message": message_dict.get("processed_plain_text"),
"processed_plain_text": message_dict.get("processed_plain_text"),
}
message_dict = {
"message_info": message_info,
"raw_message": find_msg.get("processed_plain_text"),
"processed_plain_text": find_msg.get("processed_plain_text"),
}
message_recv = MessageRecv(message_dict)
find_rec_msg = MessageRecv(message_dict)
find_rec_msg.update_chat_stream(target_stream)
logger.info(f"[SendAPI] 找到匹配的回复消息,发送者: {message_dict.get('user_nickname', '')}")
return message_recv
logger.info(f"[SendAPI] 找到匹配的回复消息,发送者: {sender}")
return find_rec_msg
except Exception as e:
logger.error(f"[SendAPI] 查找回复消息时出错: {e}")
traceback.print_exc()
return None
# =============================================================================
@@ -259,7 +207,8 @@ async def text_to_stream(
stream_id: str,
typing: bool = False,
reply_to: str = "",
reply_to_platform_id: str = "",
reply_to_message: Optional[Dict[str, Any]] = None,
set_reply: bool = False,
storage_message: bool = True,
) -> bool:
"""向指定流发送文本消息
@@ -269,7 +218,6 @@ async def text_to_stream(
stream_id: 聊天流ID
typing: 是否显示正在输入
reply_to: 回复消息,格式为"发送者:消息内容"
reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!)
storage_message: 是否存储消息到数据库
Returns:
@@ -282,12 +230,13 @@ async def text_to_stream(
"",
typing,
reply_to,
reply_to_platform_id=reply_to_platform_id,
set_reply=set_reply,
reply_to_message=reply_to_message,
storage_message=storage_message,
)
async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool:
async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True, set_reply: bool = False,reply_to_message: Optional[Dict[str, Any]] = None) -> bool:
"""向指定流发送表情包
Args:
@@ -298,10 +247,10 @@ async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bo
Returns:
bool: 是否发送成功
"""
return await _send_to_target("emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message)
return await _send_to_target("emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message, set_reply=set_reply,reply_to_message=reply_to_message)
async def image_to_stream(image_base64: str, stream_id: str, storage_message: bool = True) -> bool:
async def image_to_stream(image_base64: str, stream_id: str, storage_message: bool = True, set_reply: bool = False,reply_to_message: Optional[Dict[str, Any]] = None) -> bool:
"""向指定流发送图片
Args:
@@ -312,11 +261,11 @@ async def image_to_stream(image_base64: str, stream_id: str, storage_message: bo
Returns:
bool: 是否发送成功
"""
return await _send_to_target("image", image_base64, stream_id, "", typing=False, storage_message=storage_message)
return await _send_to_target("image", image_base64, stream_id, "", typing=False, storage_message=storage_message, set_reply=set_reply,reply_to_message=reply_to_message)
async def command_to_stream(
command: Union[str, dict], stream_id: str, storage_message: bool = True, display_message: str = ""
command: Union[str, dict], stream_id: str, storage_message: bool = True, display_message: str = "", set_reply: bool = False,reply_to_message: Optional[Dict[str, Any]] = None
) -> bool:
"""向指定流发送命令
@@ -329,7 +278,7 @@ async def command_to_stream(
bool: 是否发送成功
"""
return await _send_to_target(
"command", command, stream_id, display_message, typing=False, storage_message=storage_message
"command", command, stream_id, display_message, typing=False, storage_message=storage_message, set_reply=set_reply,reply_to_message=reply_to_message
)
@@ -340,6 +289,8 @@ async def custom_to_stream(
display_message: str = "",
typing: bool = False,
reply_to: str = "",
reply_to_message: Optional[Dict[str, Any]] = None,
set_reply: bool = False,
storage_message: bool = True,
show_log: bool = True,
) -> bool:
@@ -364,6 +315,8 @@ async def custom_to_stream(
display_message=display_message,
typing=typing,
reply_to=reply_to,
reply_to_message=reply_to_message,
set_reply=set_reply,
storage_message=storage_message,
show_log=show_log,
)

View File

@@ -228,6 +228,7 @@ class BaseAction(ABC):
stream_id=self.chat_id,
reply_to=reply_to,
typing=typing,
reply_to_message=self.action_message,
)
async def send_emoji(self, emoji_base64: str) -> bool:
@@ -243,7 +244,7 @@ class BaseAction(ABC):
logger.error(f"{self.log_prefix} 缺少聊天ID")
return False
return await send_api.emoji_to_stream(emoji_base64, self.chat_id)
return await send_api.emoji_to_stream(emoji_base64, self.chat_id,reply_to_message=self.action_message)
async def send_image(self, image_base64: str) -> bool:
"""发送图片
@@ -258,7 +259,7 @@ class BaseAction(ABC):
logger.error(f"{self.log_prefix} 缺少聊天ID")
return False
return await send_api.image_to_stream(image_base64, self.chat_id)
return await send_api.image_to_stream(image_base64, self.chat_id,reply_to_message=self.action_message)
async def send_custom(self, message_type: str, content: str, typing: bool = False, reply_to: str = "") -> bool:
"""发送自定义类型消息
@@ -282,6 +283,7 @@ class BaseAction(ABC):
stream_id=self.chat_id,
typing=typing,
reply_to=reply_to,
reply_to_message=self.action_message,
)
async def store_action_info(
@@ -336,6 +338,7 @@ class BaseAction(ABC):
stream_id=self.chat_id,
storage_message=storage_message,
display_message=display_message,
reply_to_message=self.action_message,
)
if success:

View File

@@ -100,7 +100,7 @@ class BaseCommand(ABC):
logger.error(f"{self.log_prefix} 缺少聊天流或stream_id")
return False
return await send_api.text_to_stream(text=content, stream_id=chat_stream.stream_id, reply_to=reply_to)
return await send_api.text_to_stream(text=content, stream_id=chat_stream.stream_id, reply_to=reply_to,reply_to_message=self.message)
async def send_type(
self, message_type: str, content: str, display_message: str = "", typing: bool = False, reply_to: str = ""
@@ -130,6 +130,7 @@ class BaseCommand(ABC):
display_message=display_message,
typing=typing,
reply_to=reply_to,
reply_to_message=self.message,
)
async def send_command(
@@ -161,6 +162,7 @@ class BaseCommand(ABC):
stream_id=chat_stream.stream_id,
storage_message=storage_message,
display_message=display_message,
reply_to_message=self.message,
)
if success:
@@ -188,7 +190,7 @@ class BaseCommand(ABC):
logger.error(f"{self.log_prefix} 缺少聊天流或stream_id")
return False
return await send_api.emoji_to_stream(emoji_base64, chat_stream.stream_id)
return await send_api.emoji_to_stream(emoji_base64, chat_stream.stream_id,reply_to_message=self.message)
async def send_image(self, image_base64: str) -> bool:
"""发送图片
@@ -204,7 +206,7 @@ class BaseCommand(ABC):
logger.error(f"{self.log_prefix} 缺少聊天流或stream_id")
return False
return await send_api.image_to_stream(image_base64, chat_stream.stream_id)
return await send_api.image_to_stream(image_base64, chat_stream.stream_id,reply_to_message=self.message)
@classmethod
def get_command_info(cls) -> "CommandInfo":

View File

@@ -1,5 +1,5 @@
[inner]
version = "6.3.0"
version = "6.3.1"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请递增version的值
@@ -53,26 +53,29 @@ expression_groups = [
]
[chat] #麦麦的聊天通用设置
talk_frequency = 1
# 麦麦活跃度,越高,麦麦回复越多
[chat] #麦麦的聊天设置
talk_frequency = 0.5
# 麦麦活跃度,越高,麦麦回复越多范围0-1
focus_value = 0.5
# 麦麦的专注度越高越容易持续连续对话范围0-1
focus_value = 1
# 麦麦的专注思考能力,越高越容易持续连续对话
max_context_size = 25 # 上下文长度
thinking_timeout = 40 # 麦麦一次回复最长思考规划时间超过这个时间的思考会放弃往往是api反应太慢
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
max_context_size = 20 # 上下文长度
mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复
talk_frequency_adjust = [
["", "8:00,1", "12:00,1.2", "18:00,1.5", "01:00,0.6"],
["qq:114514:group", "12:20,1", "16:10,2", "20:10,1", "00:10,0.3"],
["qq:1919810:private", "8:20,1", "12:10,2", "20:10,1.5", "00:10,0.2"]
focus_value_adjust = [
["", "8:00,1", "12:00,0.8", "18:00,1", "01:00,0.3"],
["qq:114514:group", "12:20,0.6", "16:10,0.5", "20:10,0.8", "00:10,0.3"],
["qq:1919810:private", "8:20,0.5", "12:10,0.8", "20:10,1", "00:10,0.2"]
]
# 基于聊天流的个性化活跃度配置
talk_frequency_adjust = [
["", "8:00,0.5", "12:00,0.6", "18:00,0.8", "01:00,0.3"],
["qq:114514:group", "12:20,0.3", "16:10,0.5", "20:10,0.4", "00:10,0.1"],
["qq:1919810:private", "8:20,0.3", "12:10,0.4", "20:10,0.5", "00:10,0.1"]
]
# 基于聊天流的个性化活跃度和专注度配置
# 格式:[["platform:chat_id:type", "HH:MM,frequency", "HH:MM,frequency", ...], ...]
# 全局配置示例:
@@ -109,9 +112,6 @@ ban_msgs_regex = [
#"\\d{4}-\\d{2}-\\d{2}", # 匹配日期
]
[normal_chat] #普通聊天
willing_mode = "classical" # 回复意愿模式 —— 经典模式classicalmxp模式mxp自定义模式custom需要你自己实现
[tool]
enable_tool = false # 是否在普通聊天中启用工具

View File

@@ -1,5 +1,5 @@
[inner]
version = "1.2.0"
version = "1.3.0"
# 配置文件版本号迭代规则同bot_config.toml
@@ -112,16 +112,11 @@ model_list = ["qwen3-8b"]
temperature = 0.7
max_tokens = 800
[model_task_config.replyer_1] # 首要回复模型,还用于表达器和表达方式学习
[model_task_config.replyer] # 首要回复模型,还用于表达器和表达方式学习
model_list = ["siliconflow-deepseek-v3"]
temperature = 0.2 # 模型温度新V3建议0.1-0.3
max_tokens = 800
[model_task_config.replyer_2] # 次要回复模型
model_list = ["siliconflow-deepseek-v3"]
temperature = 0.7
max_tokens = 800
[model_task_config.planner] #决策:负责决定麦麦该做什么的模型
model_list = ["siliconflow-deepseek-v3"]
temperature = 0.3