feat(planner): 引入双模动作激活机制与混合触发类型
为了更精细地控制动作在不同聊天模式下的行为,并提升决策效率,本次更新引入了全新的动作激活机制。 - **双模激活**: 为 Action 新增 `normal_activation_type` 和 `focus_activation_type` 属性,允许插件在 `NORMAL` 和 `FOCUS` 模式下拥有不同的激活策略,使行为更符合上下文。 - **混合触发**: 新增 `KEYWORD_OR_LLM_JUDGE` 激活类型。该类型会先进行快速的关键词匹配,若未匹配成功,则回退至 LLM 进行判断,兼顾了响应速度和智能化。 - **流程优化**: 重构了 `PlanGenerator` 的动作筛选逻辑,使其在生成计划前,就根据当前聊天模式和简单的激活规则进行预筛选,为后续的 LLM 决策提供更精准、更高效的候选动作列表。
This commit is contained in:
@@ -135,6 +135,16 @@ class CycleProcessor:
|
|||||||
action_type = "no_action"
|
action_type = "no_action"
|
||||||
reply_text = "" # 初始化reply_text变量,避免UnboundLocalError
|
reply_text = "" # 初始化reply_text变量,避免UnboundLocalError
|
||||||
|
|
||||||
|
|
||||||
|
# 开始新的思考循环
|
||||||
|
cycle_timers, thinking_id = self.cycle_tracker.start_cycle()
|
||||||
|
logger.info(f"{self.log_prefix} 开始第{self.context.cycle_counter}次思考")
|
||||||
|
|
||||||
|
if ENABLE_S4U and self.context.chat_stream and self.context.chat_stream.user_info:
|
||||||
|
await send_typing(self.context.chat_stream.user_info.user_id)
|
||||||
|
|
||||||
|
loop_start_time = time.time()
|
||||||
|
|
||||||
# 使用sigmoid函数将interest_value转换为概率
|
# 使用sigmoid函数将interest_value转换为概率
|
||||||
# 当interest_value为0时,概率接近0(使用Focus模式)
|
# 当interest_value为0时,概率接近0(使用Focus模式)
|
||||||
# 当interest_value很高时,概率接近1(使用Normal模式)
|
# 当interest_value很高时,概率接近1(使用Normal模式)
|
||||||
@@ -188,7 +198,7 @@ class CycleProcessor:
|
|||||||
# 第一步:动作修改
|
# 第一步:动作修改
|
||||||
with Timer("动作修改", cycle_timers):
|
with Timer("动作修改", cycle_timers):
|
||||||
try:
|
try:
|
||||||
await self.action_modifier.modify_actions()
|
await self.action_modifier.modify_actions(mode=mode)
|
||||||
available_actions = self.context.action_manager.get_using_actions()
|
available_actions = self.context.action_manager.get_using_actions()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self.context.log_prefix} 动作修改失败: {e}")
|
logger.error(f"{self.context.log_prefix} 动作修改失败: {e}")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from src.llm_models.utils_model import LLMRequest
|
|||||||
from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext
|
from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext
|
||||||
from src.chat.planner_actions.action_manager import ActionManager
|
from src.chat.planner_actions.action_manager import ActionManager
|
||||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages
|
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages
|
||||||
from src.plugin_system.base.component_types import ActionInfo, ActionActivationType
|
from src.plugin_system.base.component_types import ActionInfo, ActionActivationType, ChatMode
|
||||||
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -43,10 +43,7 @@ class ActionModifier:
|
|||||||
self._cache_expiry_time = 30 # 缓存过期时间(秒)
|
self._cache_expiry_time = 30 # 缓存过期时间(秒)
|
||||||
self._last_context_hash = None # 上次上下文的哈希值
|
self._last_context_hash = None # 上次上下文的哈希值
|
||||||
|
|
||||||
async def modify_actions(
|
async def modify_actions(self, mode: ChatMode, message_content: str = ""):
|
||||||
self,
|
|
||||||
message_content: str = "",
|
|
||||||
): # sourcery skip: use-named-expression
|
|
||||||
"""
|
"""
|
||||||
动作修改流程,整合传统观察处理和新的激活类型判定
|
动作修改流程,整合传统观察处理和新的激活类型判定
|
||||||
|
|
||||||
@@ -146,6 +143,7 @@ class ActionModifier:
|
|||||||
removals_s3 = await self._get_deactivated_actions_by_type(
|
removals_s3 = await self._get_deactivated_actions_by_type(
|
||||||
current_using_actions,
|
current_using_actions,
|
||||||
chat_content,
|
chat_content,
|
||||||
|
mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 应用第三阶段的移除
|
# 应用第三阶段的移除
|
||||||
@@ -178,6 +176,7 @@ class ActionModifier:
|
|||||||
self,
|
self,
|
||||||
actions_with_info: Dict[str, ActionInfo],
|
actions_with_info: Dict[str, ActionInfo],
|
||||||
chat_content: str = "",
|
chat_content: str = "",
|
||||||
|
mode: ChatMode = ChatMode.NORMAL,
|
||||||
) -> List[tuple[str, str]]:
|
) -> List[tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
根据激活类型过滤,返回需要停用的动作列表及原因
|
根据激活类型过滤,返回需要停用的动作列表及原因
|
||||||
@@ -198,34 +197,26 @@ class ActionModifier:
|
|||||||
random.shuffle(actions_to_check)
|
random.shuffle(actions_to_check)
|
||||||
|
|
||||||
for action_name, action_info in actions_to_check:
|
for action_name, action_info in actions_to_check:
|
||||||
activation_type = action_info.activation_type or action_info.focus_activation_type
|
if mode == ChatMode.FOCUS:
|
||||||
|
activation_type = action_info.focus_activation_type
|
||||||
|
else:
|
||||||
|
activation_type = action_info.normal_activation_type
|
||||||
|
|
||||||
if activation_type == ActionActivationType.ALWAYS:
|
if activation_type == ActionActivationType.ALWAYS:
|
||||||
continue # 总是激活,无需处理
|
continue
|
||||||
|
|
||||||
elif activation_type == ActionActivationType.RANDOM:
|
elif activation_type == ActionActivationType.RANDOM:
|
||||||
probability = action_info.random_activation_probability
|
if random.random() >= action_info.random_activation_probability:
|
||||||
probability = action_info.random_activation_probability
|
deactivated_actions.append((action_name, f"RANDOM类型未触发(概率{action_info.random_activation_probability})"))
|
||||||
if random.random() >= probability:
|
|
||||||
reason = f"RANDOM类型未触发(概率{probability})"
|
|
||||||
deactivated_actions.append((action_name, reason))
|
|
||||||
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: {reason}")
|
|
||||||
|
|
||||||
elif activation_type == ActionActivationType.KEYWORD:
|
elif activation_type == ActionActivationType.KEYWORD:
|
||||||
if not self._check_keyword_activation(action_name, action_info, chat_content):
|
if not self._check_keyword_activation(action_name, action_info, chat_content):
|
||||||
keywords = action_info.activation_keywords
|
deactivated_actions.append((action_name, f"关键词未匹配(关键词: {action_info.activation_keywords})"))
|
||||||
reason = f"关键词未匹配(关键词: {keywords})"
|
|
||||||
deactivated_actions.append((action_name, reason))
|
|
||||||
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: {reason}")
|
|
||||||
|
|
||||||
elif activation_type == ActionActivationType.LLM_JUDGE:
|
elif activation_type == ActionActivationType.LLM_JUDGE:
|
||||||
llm_judge_actions[action_name] = action_info
|
llm_judge_actions[action_name] = action_info
|
||||||
|
elif activation_type == ActionActivationType.KEYWORD_OR_LLM_JUDGE:
|
||||||
|
if not self._check_keyword_activation(action_name, action_info, chat_content):
|
||||||
|
llm_judge_actions[action_name] = action_info
|
||||||
elif activation_type == ActionActivationType.NEVER:
|
elif activation_type == ActionActivationType.NEVER:
|
||||||
reason = "激活类型为never"
|
deactivated_actions.append((action_name, "激活类型为never"))
|
||||||
deactivated_actions.append((action_name, reason))
|
|
||||||
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: 激活类型为never")
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理")
|
logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理")
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
PlanGenerator: 负责搜集和汇总所有决策所需的信息,生成一个未经筛选的“原始计划” (Plan)。
|
PlanGenerator: 负责搜集和汇总所有决策所需的信息,生成一个未经筛选的“原始计划” (Plan)。
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
from typing import Dict, Optional, Tuple
|
from typing import Dict, Optional, Tuple, List
|
||||||
|
|
||||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat
|
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat
|
||||||
from src.chat.utils.utils import get_chat_type_and_target_info
|
from src.chat.utils.utils import get_chat_type_and_target_info
|
||||||
from src.common.data_models.database_data_model import DatabaseMessages
|
from src.common.data_models.database_data_model import DatabaseMessages
|
||||||
from src.common.data_models.info_data_model import Plan, TargetPersonInfo
|
from src.common.data_models.info_data_model import Plan, TargetPersonInfo
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.plugin_system.base.component_types import ActionInfo, ChatMode, ComponentType
|
from src.plugin_system.base.component_types import ActionActivationType, ActionInfo, ChatMode, ComponentType
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
|
|
||||||
|
|
||||||
@@ -57,13 +57,13 @@ class PlanGenerator:
|
|||||||
if chat_target_info_dict:
|
if chat_target_info_dict:
|
||||||
target_info = TargetPersonInfo(**chat_target_info_dict)
|
target_info = TargetPersonInfo(**chat_target_info_dict)
|
||||||
|
|
||||||
available_actions = self._get_available_actions()
|
|
||||||
chat_history_raw = get_raw_msg_before_timestamp_with_chat(
|
chat_history_raw = get_raw_msg_before_timestamp_with_chat(
|
||||||
chat_id=self.chat_id,
|
chat_id=self.chat_id,
|
||||||
timestamp=time.time(),
|
timestamp=time.time(),
|
||||||
limit=int(global_config.chat.max_context_size),
|
limit=int(global_config.chat.max_context_size),
|
||||||
)
|
)
|
||||||
chat_history = [DatabaseMessages(**msg) for msg in chat_history_raw]
|
chat_history = [DatabaseMessages(**msg) for msg in chat_history_raw]
|
||||||
|
available_actions = self._get_available_actions(mode, chat_history)
|
||||||
|
|
||||||
|
|
||||||
plan = Plan(
|
plan = Plan(
|
||||||
@@ -75,36 +75,41 @@ class PlanGenerator:
|
|||||||
)
|
)
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
def _get_available_actions(self) -> Dict[str, "ActionInfo"]:
|
def _get_available_actions(self, mode: ChatMode, chat_history: List[DatabaseMessages]) -> Dict[str, "ActionInfo"]:
|
||||||
"""
|
"""
|
||||||
从 ActionManager 和组件注册表中获取当前所有可用的动作。
|
根据当前的聊天模式和激活类型,筛选出可用的动作。
|
||||||
|
|
||||||
它会合并已注册的动作和系统级动作(如 "no_reply"),
|
|
||||||
并以字典形式返回。
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict[str, "ActionInfo"]: 一个字典,键是动作名称,值是 ActionInfo 对象。
|
|
||||||
"""
|
"""
|
||||||
current_available_actions_dict = self.action_manager.get_using_actions()
|
all_actions: Dict[str, ActionInfo] = component_registry.get_components_by_type(ComponentType.ACTION) # type: ignore
|
||||||
all_registered_actions: Dict[str, ActionInfo] = component_registry.get_components_by_type( # type: ignore
|
available_actions = {}
|
||||||
ComponentType.ACTION
|
latest_message_text = chat_history[-1].processed_plain_text if chat_history else ""
|
||||||
)
|
|
||||||
|
|
||||||
current_available_actions = {}
|
|
||||||
for action_name in current_available_actions_dict:
|
|
||||||
if action_name in all_registered_actions:
|
|
||||||
current_available_actions[action_name] = all_registered_actions[action_name]
|
|
||||||
|
|
||||||
|
for name, info in all_actions.items():
|
||||||
|
# 根据当前模式选择对应的激活类型
|
||||||
|
activation_type = info.focus_activation_type if mode == ChatMode.FOCUS else info.normal_activation_type
|
||||||
|
|
||||||
|
if activation_type in [ActionActivationType.ALWAYS, ActionActivationType.LLM_JUDGE]:
|
||||||
|
available_actions[name] = info
|
||||||
|
elif activation_type == ActionActivationType.KEYWORD:
|
||||||
|
if any(kw.lower() in latest_message_text.lower() for kw in info.activation_keywords):
|
||||||
|
available_actions[name] = info
|
||||||
|
elif activation_type == ActionActivationType.KEYWORD_OR_LLM_JUDGE:
|
||||||
|
if any(kw.lower() in latest_message_text.lower() for kw in info.activation_keywords):
|
||||||
|
available_actions[name] = info
|
||||||
|
else:
|
||||||
|
# 即使关键词不匹配,也将其添加到可用动作中,交由LLM判断
|
||||||
|
available_actions[name] = info
|
||||||
|
elif activation_type == ActionActivationType.NEVER:
|
||||||
|
pass # 永不激活
|
||||||
|
else:
|
||||||
|
logger.warning(f"未知的激活类型: {activation_type},跳过处理")
|
||||||
|
|
||||||
|
# 添加系统级动作
|
||||||
no_reply_info = ActionInfo(
|
no_reply_info = ActionInfo(
|
||||||
name="no_reply",
|
name="no_reply",
|
||||||
component_type=ComponentType.ACTION,
|
component_type=ComponentType.ACTION,
|
||||||
description="系统级动作:选择不回复消息的决策",
|
description="系统级动作:选择不回复消息的决策",
|
||||||
action_parameters={},
|
|
||||||
activation_keywords=[],
|
|
||||||
plugin_name="SYSTEM",
|
plugin_name="SYSTEM",
|
||||||
enabled=True,
|
|
||||||
parallel_action=False,
|
|
||||||
)
|
)
|
||||||
current_available_actions["no_reply"] = no_reply_info
|
available_actions["no_reply"] = no_reply_info
|
||||||
|
|
||||||
return current_available_actions
|
return available_actions
|
||||||
@@ -459,7 +459,6 @@ class BaseAction(ABC):
|
|||||||
focus_activation_type = getattr(cls, "focus_activation_type", ActionActivationType.ALWAYS)
|
focus_activation_type = getattr(cls, "focus_activation_type", ActionActivationType.ALWAYS)
|
||||||
normal_activation_type = getattr(cls, "normal_activation_type", ActionActivationType.ALWAYS)
|
normal_activation_type = getattr(cls, "normal_activation_type", ActionActivationType.ALWAYS)
|
||||||
|
|
||||||
# 处理activation_type:如果插件中声明了就用插件的值,否则默认使用focus_activation_type
|
|
||||||
activation_type = getattr(cls, "activation_type", focus_activation_type)
|
activation_type = getattr(cls, "activation_type", focus_activation_type)
|
||||||
|
|
||||||
return ActionInfo(
|
return ActionInfo(
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ActionActivationType(Enum):
|
|||||||
LLM_JUDGE = "llm_judge" # LLM判定是否启动该action到planner
|
LLM_JUDGE = "llm_judge" # LLM判定是否启动该action到planner
|
||||||
RANDOM = "random" # 随机启用action到planner
|
RANDOM = "random" # 随机启用action到planner
|
||||||
KEYWORD = "keyword" # 关键词触发启用action到planner
|
KEYWORD = "keyword" # 关键词触发启用action到planner
|
||||||
|
KEYWORD_OR_LLM_JUDGE = "keyword_or_llm_judge" # From Elysia with love~ 献给我最棒的伙伴!
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|||||||
Reference in New Issue
Block a user