feat:将action记录置入planner,有效减少重复调用action问题
This commit is contained in:
@@ -51,7 +51,6 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]:
|
||||
is_mentioned, _ = is_mentioned_bot_in_message(message)
|
||||
interested_rate = 0.0
|
||||
|
||||
if global_config.memory.enable_memory:
|
||||
with Timer("记忆激活"):
|
||||
interested_rate = await hippocampus_manager.get_activate_from_text(
|
||||
message.processed_plain_text,
|
||||
|
||||
@@ -11,7 +11,7 @@ from json_repair import repair_json
|
||||
from src.chat.utils.utils import get_chat_type_and_target_info
|
||||
from datetime import datetime
|
||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
|
||||
from src.chat.utils.chat_message_builder import build_readable_actions, build_readable_messages, get_actions_by_timestamp_with_chat, get_raw_msg_before_timestamp_with_chat
|
||||
import time
|
||||
|
||||
logger = get_logger("planner")
|
||||
@@ -27,6 +27,9 @@ def init_prompt():
|
||||
你现在需要根据聊天内容,选择的合适的action来参与聊天。
|
||||
{chat_context_description},以下是具体的聊天内容:
|
||||
{chat_content_block}
|
||||
你刚刚进行过的action是:
|
||||
{actions_before_now_block}
|
||||
|
||||
{moderation_prompt}
|
||||
|
||||
现在请你根据{by_what}选择合适的action:
|
||||
@@ -222,6 +225,16 @@ class ActionPlanner:
|
||||
show_actions=True,
|
||||
)
|
||||
|
||||
actions_before_now = get_actions_by_timestamp_with_chat(
|
||||
chat_id=self.chat_id,
|
||||
timestamp_end=time.time(),
|
||||
limit=5,
|
||||
)
|
||||
|
||||
actions_before_now_block = build_readable_actions(
|
||||
actions=actions_before_now,
|
||||
)
|
||||
|
||||
self.last_obs_time_mark = time.time()
|
||||
|
||||
if self.mode == "focus":
|
||||
@@ -285,6 +298,7 @@ class ActionPlanner:
|
||||
by_what=by_what,
|
||||
chat_context_description=chat_context_description,
|
||||
chat_content_block=chat_content_block,
|
||||
actions_before_now_block=actions_before_now_block,
|
||||
no_action_block=no_action_block,
|
||||
action_options_text=action_options_block,
|
||||
moderation_prompt=moderation_prompt_block,
|
||||
|
||||
@@ -77,6 +77,56 @@ def get_raw_msg_by_timestamp_with_chat_users(
|
||||
return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode)
|
||||
|
||||
|
||||
def get_actions_by_timestamp_with_chat(
|
||||
chat_id: str, timestamp_start: float = 0, timestamp_end: float = time.time(), limit: int = 0, limit_mode: str = "latest"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""获取在特定聊天从指定时间戳到指定时间戳的动作记录,按时间升序排序,返回动作记录列表"""
|
||||
query = ActionRecords.select().where(
|
||||
(ActionRecords.chat_id == chat_id)
|
||||
& (ActionRecords.time > timestamp_start)
|
||||
& (ActionRecords.time < timestamp_end)
|
||||
)
|
||||
|
||||
if limit > 0:
|
||||
if limit_mode == "latest":
|
||||
query = query.order_by(ActionRecords.time.desc()).limit(limit)
|
||||
# 获取后需要反转列表,以保持最终输出为时间升序
|
||||
actions = list(query)
|
||||
return [action.__data__ for action in reversed(actions)]
|
||||
else: # earliest
|
||||
query = query.order_by(ActionRecords.time.asc()).limit(limit)
|
||||
else:
|
||||
query = query.order_by(ActionRecords.time.asc())
|
||||
|
||||
actions = list(query)
|
||||
return [action.__data__ for action in actions]
|
||||
|
||||
|
||||
def get_actions_by_timestamp_with_chat_inclusive(
|
||||
chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""获取在特定聊天从指定时间戳到指定时间戳的动作记录(包含边界),按时间升序排序,返回动作记录列表"""
|
||||
query = ActionRecords.select().where(
|
||||
(ActionRecords.chat_id == chat_id)
|
||||
& (ActionRecords.time >= timestamp_start)
|
||||
& (ActionRecords.time <= timestamp_end)
|
||||
)
|
||||
|
||||
if limit > 0:
|
||||
if limit_mode == "latest":
|
||||
query = query.order_by(ActionRecords.time.desc()).limit(limit)
|
||||
# 获取后需要反转列表,以保持最终输出为时间升序
|
||||
actions = list(query)
|
||||
return [action.__data__ for action in reversed(actions)]
|
||||
else: # earliest
|
||||
query = query.order_by(ActionRecords.time.asc()).limit(limit)
|
||||
else:
|
||||
query = query.order_by(ActionRecords.time.asc())
|
||||
|
||||
actions = list(query)
|
||||
return [action.__data__ for action in actions]
|
||||
|
||||
|
||||
def get_raw_msg_by_timestamp_random(
|
||||
timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest"
|
||||
) -> List[Dict[str, Any]]:
|
||||
@@ -503,6 +553,45 @@ def build_pic_mapping_info(pic_id_mapping: Dict[str, str]) -> str:
|
||||
return "\n".join(mapping_lines)
|
||||
|
||||
|
||||
def build_readable_actions(actions: List[Dict[str, Any]]) -> str:
|
||||
"""
|
||||
将动作列表转换为可读的文本格式。
|
||||
格式: 在()分钟前,你使用了(action_name),具体内容是:(action_prompt_display)
|
||||
|
||||
Args:
|
||||
actions: 动作记录字典列表。
|
||||
|
||||
Returns:
|
||||
格式化的动作字符串。
|
||||
"""
|
||||
if not actions:
|
||||
return ""
|
||||
|
||||
output_lines = []
|
||||
current_time = time.time()
|
||||
|
||||
# The get functions return actions sorted ascending by time. Let's reverse it to show newest first.
|
||||
sorted_actions = sorted(actions, key=lambda x: x.get("time", 0), reverse=True)
|
||||
|
||||
for action in sorted_actions:
|
||||
action_time = action.get("time", current_time)
|
||||
action_name = action.get("action_name", "未知动作")
|
||||
action_prompt_display = action.get("action_prompt_display", "无具体内容")
|
||||
|
||||
time_diff_seconds = current_time - action_time
|
||||
|
||||
if time_diff_seconds < 60:
|
||||
time_ago_str = f"在{time_diff_seconds}秒前"
|
||||
else:
|
||||
time_diff_minutes = round(time_diff_seconds / 60)
|
||||
time_ago_str = f"在{time_diff_minutes}分钟前"
|
||||
|
||||
line = f"{time_ago_str},你使用了“{action_name}”,具体内容是:“{action_prompt_display}”"
|
||||
output_lines.append(line)
|
||||
|
||||
return "\n".join(output_lines)
|
||||
|
||||
|
||||
async def build_readable_messages_with_list(
|
||||
messages: List[Dict[str, Any]],
|
||||
replace_bot_name: bool = True,
|
||||
|
||||
@@ -9,6 +9,7 @@ import random
|
||||
import time
|
||||
from typing import List, Tuple, Type
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
# 导入新插件系统
|
||||
from src.plugin_system import BasePlugin, register_plugin, BaseAction, ComponentInfo, ActionActivationType, ChatMode
|
||||
@@ -54,12 +55,28 @@ class ReplyAction(BaseAction):
|
||||
# 关联类型
|
||||
associated_types = ["text"]
|
||||
|
||||
def _parse_reply_target(self, target_message: str) -> tuple:
|
||||
sender = ""
|
||||
target = ""
|
||||
if ":" in target_message or ":" in target_message:
|
||||
# 使用正则表达式匹配中文或英文冒号
|
||||
parts = re.split(pattern=r"[::]", string=target_message, maxsplit=1)
|
||||
if len(parts) == 2:
|
||||
sender = parts[0].strip()
|
||||
target = parts[1].strip()
|
||||
return sender, target
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""执行回复动作"""
|
||||
logger.info(f"{self.log_prefix} 决定进行回复")
|
||||
|
||||
start_time = self.action_data.get("loop_start_time", time.time())
|
||||
|
||||
reply_to = self.action_data.get("reply_to", "")
|
||||
sender, target = self._parse_reply_target(reply_to)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
try:
|
||||
success, reply_set = await asyncio.wait_for(
|
||||
@@ -105,6 +122,11 @@ class ReplyAction(BaseAction):
|
||||
reply_text += data
|
||||
|
||||
# 存储动作记录
|
||||
if sender and target:
|
||||
reply_text = f"你对{sender}说的{target},进行了回复:{reply_text}"
|
||||
else:
|
||||
reply_text = f"你进行发言:{reply_text}"
|
||||
|
||||
await self.store_action_info(
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=reply_text,
|
||||
@@ -148,31 +170,23 @@ class CoreActionsPlugin(BasePlugin):
|
||||
# 配置Schema定义
|
||||
config_schema = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="0.3.1", description="配置文件版本"),
|
||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="0.4.0", description="配置文件版本"),
|
||||
},
|
||||
"components": {
|
||||
"enable_reply": ConfigField(type=bool, default=True, description="是否启用'回复'动作"),
|
||||
"enable_no_reply": ConfigField(type=bool, default=True, description="是否启用'不回复'动作"),
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用'表情'动作"),
|
||||
"enable_reply": ConfigField(type=bool, default=True, description="是否启用回复动作"),
|
||||
"enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"),
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
|
||||
},
|
||||
"no_reply": {
|
||||
"max_timeout": ConfigField(type=int, default=1200, description="最大等待超时时间(秒)"),
|
||||
"min_judge_interval": ConfigField(
|
||||
type=float, default=1.0, description="LLM判断的最小间隔时间(秒),防止过于频繁"
|
||||
),
|
||||
"auto_exit_message_count": ConfigField(
|
||||
type=int, default=20, description="累计消息数量达到此阈值时自动结束等待"
|
||||
"interest_exit_threshold": ConfigField(
|
||||
type=float, default=3.0, description="累计兴趣值达到此阈值时自动结束等待"
|
||||
),
|
||||
"min_exit_message_count": ConfigField(type=int, default=6, description="自动结束等待的最小消息数"),
|
||||
"max_exit_message_count": ConfigField(type=int, default=9, description="自动结束等待的最大消息数"),
|
||||
"random_probability": ConfigField(
|
||||
type=float, default=0.8, description="Focus模式下,随机选择不回复的概率(0.0到1.0)", example=0.8
|
||||
),
|
||||
"skip_judge_when_tired": ConfigField(
|
||||
type=bool, default=True, description="当发言过多时是否启用跳过LLM判断机制"
|
||||
),
|
||||
"frequency_check_window": ConfigField(
|
||||
type=int, default=600, description="回复频率检查窗口时间(秒)", example=600
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -193,21 +207,14 @@ class CoreActionsPlugin(BasePlugin):
|
||||
no_reply_probability = self.get_config("no_reply.random_probability", 0.8)
|
||||
NoReplyAction.random_activation_probability = no_reply_probability
|
||||
|
||||
min_judge_interval = self.get_config("no_reply.min_judge_interval", 1.0)
|
||||
NoReplyAction._min_judge_interval = min_judge_interval
|
||||
interest_exit_threshold = self.get_config("no_reply.interest_exit_threshold", 10.0)
|
||||
NoReplyAction._interest_exit_threshold = interest_exit_threshold
|
||||
|
||||
auto_exit_message_count = self.get_config("no_reply.auto_exit_message_count", 20)
|
||||
NoReplyAction._auto_exit_message_count = auto_exit_message_count
|
||||
min_exit_count = self.get_config("no_reply.min_exit_message_count", 5)
|
||||
NoReplyAction._min_exit_message_count = min_exit_count
|
||||
|
||||
max_timeout = self.get_config("no_reply.max_timeout", 600)
|
||||
NoReplyAction._max_timeout = max_timeout
|
||||
|
||||
skip_judge_when_tired = self.get_config("no_reply.skip_judge_when_tired", True)
|
||||
NoReplyAction._skip_judge_when_tired = skip_judge_when_tired
|
||||
|
||||
# 新增:频率检测相关配置
|
||||
frequency_check_window = self.get_config("no_reply.frequency_check_window", 600)
|
||||
NoReplyAction._frequency_check_window = frequency_check_window
|
||||
max_exit_count = self.get_config("no_reply.max_exit_message_count", 10)
|
||||
NoReplyAction._max_exit_message_count = max_exit_count
|
||||
|
||||
# --- 根据配置注册组件 ---
|
||||
components = []
|
||||
|
||||
Reference in New Issue
Block a user