feat:给动作添加了选择器,并添加了新api

This commit is contained in:
SengokuCola
2025-06-09 12:55:23 +08:00
parent 5ef3139654
commit 2ce5114b8c
19 changed files with 1660 additions and 103 deletions

View File

@@ -441,31 +441,33 @@ class HeartFChatting:
"observations": self.observations,
}
with Timer("调整动作", cycle_timers):
# 处理特殊的观察
await self.action_modifier.modify_actions(observations=self.observations)
await self.action_observation.observe()
self.observations.append(self.action_observation)
# 根据配置决定是否并行执行调整动作、回忆和处理器阶段
# 根据配置决定是否并行执行回忆和处理器阶段
# print(global_config.focus_chat.parallel_processing)
if global_config.focus_chat.parallel_processing:
# 并行执行回忆和处理器阶段
with Timer("并行回忆和处理", cycle_timers):
memory_task = asyncio.create_task(self.memory_activator.activate_memory(self.observations))
processor_task = asyncio.create_task(self._process_processors(self.observations, []))
# 等待两个任务完成
running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(
memory_task, processor_task
# 并行执行调整动作、回忆和处理器阶段
with Timer("并行调整动作、处理", cycle_timers):
# 创建并行任务
async def modify_actions_task():
# 调用完整的动作修改流程
await self.action_modifier.modify_actions(
observations=self.observations,
)
else:
# 串行执行
with Timer("回忆", cycle_timers):
running_memorys = await self.memory_activator.activate_memory(self.observations)
await self.action_observation.observe()
self.observations.append(self.action_observation)
return True
# 创建三个并行任务
action_modify_task = asyncio.create_task(modify_actions_task())
memory_task = asyncio.create_task(self.memory_activator.activate_memory(self.observations))
processor_task = asyncio.create_task(self._process_processors(self.observations, []))
# 等待三个任务完成
_, running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(
action_modify_task, memory_task, processor_task
)
with Timer("执行 信息处理器", cycle_timers):
all_plan_info, processor_time_costs = await self._process_processors(self.observations, running_memorys)
loop_processor_info = {
"all_plan_info": all_plan_info,

View File

@@ -60,6 +60,13 @@ class ActionManager:
action_require: list[str] = getattr(action_class, "action_require", [])
associated_types: list[str] = getattr(action_class, "associated_types", [])
is_default: bool = getattr(action_class, "default", False)
# 获取激活类型相关属性
activation_type: str = getattr(action_class, "action_activation_type", "always")
random_probability: float = getattr(action_class, "random_activation_probability", 0.3)
llm_judge_prompt: str = getattr(action_class, "llm_judge_prompt", "")
activation_keywords: list[str] = getattr(action_class, "activation_keywords", [])
keyword_case_sensitive: bool = getattr(action_class, "keyword_case_sensitive", False)
if action_name and action_description:
# 创建动作信息字典
@@ -68,6 +75,11 @@ class ActionManager:
"parameters": action_parameters,
"require": action_require,
"associated_types": associated_types,
"activation_type": activation_type,
"random_probability": random_probability,
"llm_judge_prompt": llm_judge_prompt,
"activation_keywords": activation_keywords,
"keyword_case_sensitive": keyword_case_sensitive,
}
# 添加到所有已注册的动作

View File

@@ -8,6 +8,12 @@ logger = get_logger("base_action")
_ACTION_REGISTRY: Dict[str, Type["BaseAction"]] = {}
_DEFAULT_ACTIONS: Dict[str, str] = {}
# 动作激活类型枚举
class ActionActivationType:
ALWAYS = "always" # 默认参与到planner
LLM_JUDGE = "llm_judge" # LLM判定是否启动该action到planner
RANDOM = "random" # 随机启用action到planner
KEYWORD = "keyword" # 关键词触发启用action到planner
def register_action(cls):
"""
@@ -18,6 +24,7 @@ def register_action(cls):
class MyAction(BaseAction):
action_name = "my_action"
action_description = "我的动作"
action_activation_type = ActionActivationType.ALWAYS
...
"""
# 检查类是否有必要的属性
@@ -65,6 +72,17 @@ class BaseAction(ABC):
self.action_description: str = "基础动作"
self.action_parameters: dict = {}
self.action_require: list[str] = []
# 动作激活类型默认为always
self.action_activation_type: str = ActionActivationType.ALWAYS
# 随机激活的概率(0.0-1.0)仅当activation_type为random时有效
self.random_activation_probability: float = 0.3
# LLM判定的提示词仅当activation_type为llm_judge时有效
self.llm_judge_prompt: str = ""
# 关键词触发列表仅当activation_type为keyword时有效
self.activation_keywords: list[str] = []
# 关键词匹配是否区分大小写
self.keyword_case_sensitive: bool = False
self.associated_types: list[str] = []

View File

@@ -2,7 +2,7 @@ import asyncio
import traceback
from src.common.logger_manager import get_logger
from src.chat.utils.timer_calculator import Timer
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
from typing import Tuple, List
from src.chat.heart_flow.observation.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
@@ -29,6 +29,9 @@ class NoReplyAction(BaseAction):
"想要休息一下",
]
default = True
# 激活类型设置
action_activation_type = ActionActivationType.ALWAYS
def __init__(
self,

View File

@@ -1,6 +1,6 @@
import traceback
from typing import Tuple, Dict, List, Any, Optional, Union, Type
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action # noqa F401
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType # noqa F401
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
from src.common.logger_manager import get_logger
@@ -33,6 +33,13 @@ class PluginAction(BaseAction):
"""
action_config_file_name: Optional[str] = None # 插件可以覆盖此属性来指定配置文件名
# 默认激活类型设置,插件可以覆盖
action_activation_type = ActionActivationType.ALWAYS
random_activation_probability: float = 0.3
llm_judge_prompt: str = ""
activation_keywords: list[str] = []
keyword_case_sensitive: bool = False
def __init__(
self,

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from src.common.logger_manager import get_logger
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
from typing import Tuple, List
from src.chat.heart_flow.observation.observation import Observation
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
@@ -38,6 +38,9 @@ class ReplyAction(BaseAction):
associated_types: list[str] = ["text", "emoji"]
default = True
# 激活类型设置
action_activation_type = ActionActivationType.ALWAYS
def __init__(
self,

View File

@@ -1,12 +1,16 @@
from typing import List, Optional, Any
from typing import List, Optional, Any, Dict
from src.chat.heart_flow.observation.observation import Observation
from src.common.logger_manager import get_logger
from src.chat.heart_flow.observation.hfcloop_observation import HFCloopObservation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.message_receive.chat_stream import chat_manager
from typing import Dict
from src.config.config import global_config
from src.llm_models.utils_model import LLMRequest
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType
import random
import asyncio
import hashlib
import time
from src.chat.focus_chat.planners.action_manager import ActionManager
logger = get_logger("action_manager")
@@ -15,25 +19,47 @@ logger = get_logger("action_manager")
class ActionModifier:
"""动作处理器
用于处理Observation对象将其转换为ObsInfo对象
用于处理Observation对象和根据激活类型处理actions
集成了原有的modify_actions功能和新的激活类型处理功能。
支持并行判定和智能缓存优化。
"""
log_prefix = "动作处理"
def __init__(self, action_manager: ActionManager):
"""初始化观察处理器"""
"""初始化动作处理器"""
self.action_manager = action_manager
self.all_actions = self.action_manager.get_registered_actions()
# 用于LLM判定的小模型
self.llm_judge = LLMRequest(
model=global_config.model.utils_small,
request_type="action.judge",
)
# 缓存相关属性
self._llm_judge_cache = {} # 缓存LLM判定结果
self._cache_expiry_time = 30 # 缓存过期时间(秒)
self._last_context_hash = None # 上次上下文的哈希值
async def modify_actions(
self,
observations: Optional[List[Observation]] = None,
**kwargs: Any,
):
# 处理Observation对象
"""
完整的动作修改流程,整合传统观察处理和新的激活类型判定
这个方法处理完整的动作管理流程:
1. 基于观察的传统动作修改(循环历史分析、类型匹配等)
2. 基于激活类型的智能动作判定,最终确定可用动作集
处理后ActionManager 将包含最终的可用动作集,供规划器直接使用
"""
logger.debug(f"{self.log_prefix}开始完整动作修改流程")
# === 第一阶段:传统观察处理 ===
if observations:
# action_info = ActionInfo()
# all_actions = None
hfc_obs = None
chat_obs = None
@@ -43,12 +69,13 @@ class ActionModifier:
hfc_obs = obs
if isinstance(obs, ChattingObservation):
chat_obs = obs
chat_content = obs.talking_message_str_truncate
# 合并所有动作变更
merged_action_changes = {"add": [], "remove": []}
reasons = []
# 处理HFCloopObservation
# 处理HFCloopObservation - 传统的循环历史分析
if hfc_obs:
obs = hfc_obs
all_actions = self.all_actions
@@ -57,14 +84,15 @@ class ActionModifier:
# 合并动作变更
merged_action_changes["add"].extend(action_changes["add"])
merged_action_changes["remove"].extend(action_changes["remove"])
reasons.append("基于循环历史分析")
# 详细记录循环历史分析的变更原因
for action_name in action_changes["add"]:
logger.info(f"{self.log_prefix}添加动作: {action_name},原因: 循环历史分析建议添加")
for action_name in action_changes["remove"]:
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: 循环历史分析建议移除")
# 收集变更原因
# if action_changes["add"]:
# reasons.append(f"添加动作{action_changes['add']}因为检测到大量无回复")
# if action_changes["remove"]:
# reasons.append(f"移除动作{action_changes['remove']}因为检测到连续回复")
# 处理ChattingObservation
# 处理ChattingObservation - 传统的类型匹配检查
if chat_obs:
obs = chat_obs
# 检查动作的关联类型
@@ -76,30 +104,431 @@ class ActionModifier:
if data.get("associated_types"):
if not chat_context.check_types(data["associated_types"]):
type_mismatched_actions.append(action_name)
logger.debug(f"{self.log_prefix} 动作 {action_name} 关联类型不匹配,移除该动作")
associated_types_str = ", ".join(data["associated_types"])
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: 关联类型不匹配(需要: {associated_types_str}")
if type_mismatched_actions:
# 合并到移除列表中
merged_action_changes["remove"].extend(type_mismatched_actions)
reasons.append(f"移除动作{type_mismatched_actions}因为关联类型不匹配")
reasons.append("基于关联类型检查")
# 应用传统的动作变更到ActionManager
for action_name in merged_action_changes["add"]:
if action_name in self.action_manager.get_registered_actions():
self.action_manager.add_action_to_using(action_name)
logger.debug(f"{self.log_prefix} 添加动作: {action_name}, 原因: {reasons}")
logger.debug(f"{self.log_prefix}应用添加动作: {action_name},原因集合: {reasons}")
for action_name in merged_action_changes["remove"]:
self.action_manager.remove_action_from_using(action_name)
logger.debug(f"{self.log_prefix} 移除动作: {action_name}, 原因: {reasons}")
logger.debug(f"{self.log_prefix}应用移除动作: {action_name},原因集合: {reasons}")
# 如果有任何动作变更设置到action_info中
# if merged_action_changes["add"] or merged_action_changes["remove"]:
# action_info.set_action_changes(merged_action_changes)
# action_info.set_reason(" | ".join(reasons))
logger.info(f"{self.log_prefix}传统动作修改完成,当前使用动作: {list(self.action_manager.get_using_actions().keys())}")
# processed_infos.append(action_info)
# === 第二阶段:激活类型判定 ===
# 如果提供了聊天上下文,则进行激活类型判定
if chat_content is not None:
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
# 获取当前使用的动作集(经过第一阶段处理)
current_using_actions = self.action_manager.get_using_actions()
all_registered_actions = self.action_manager.get_registered_actions()
# 构建完整的动作信息
current_actions_with_info = {}
for action_name in current_using_actions.keys():
if action_name in all_registered_actions:
current_actions_with_info[action_name] = all_registered_actions[action_name]
else:
logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到")
# 应用激活类型判定
final_activated_actions = await self._apply_activation_type_filtering(
current_actions_with_info,
chat_content,
)
# 更新ActionManager移除未激活的动作
actions_to_remove = []
removal_reasons = {}
for action_name in current_using_actions.keys():
if action_name not in final_activated_actions:
actions_to_remove.append(action_name)
# 确定移除原因
if action_name in all_registered_actions:
action_info = all_registered_actions[action_name]
activation_type = action_info.get("activation_type", ActionActivationType.ALWAYS)
if activation_type == ActionActivationType.RANDOM:
probability = action_info.get("random_probability", 0.3)
removal_reasons[action_name] = f"RANDOM类型未触发概率{probability}"
elif activation_type == ActionActivationType.LLM_JUDGE:
removal_reasons[action_name] = "LLM判定未激活"
elif activation_type == ActionActivationType.KEYWORD:
keywords = action_info.get("activation_keywords", [])
removal_reasons[action_name] = f"关键词未匹配(关键词: {keywords}"
else:
removal_reasons[action_name] = "激活判定未通过"
else:
removal_reasons[action_name] = "动作信息不完整"
for action_name in actions_to_remove:
self.action_manager.remove_action_from_using(action_name)
reason = removal_reasons.get(action_name, "未知原因")
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: {reason}")
logger.info(f"{self.log_prefix}激活类型判定完成,最终可用动作: {list(final_activated_actions.keys())}")
logger.info(f"{self.log_prefix}完整动作修改流程结束,最终动作集: {list(self.action_manager.get_using_actions().keys())}")
# return processed_infos
async def _apply_activation_type_filtering(
self,
actions_with_info: Dict[str, Any],
chat_content: str = "",
) -> Dict[str, Any]:
"""
应用激活类型过滤逻辑,支持四种激活类型的并行处理
Args:
actions_with_info: 带完整信息的动作字典
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文信息
extra_context: 额外的上下文信息
Returns:
Dict[str, Any]: 过滤后激活的actions字典
"""
activated_actions = {}
# 分类处理不同激活类型的actions
always_actions = {}
random_actions = {}
llm_judge_actions = {}
keyword_actions = {}
for action_name, action_info in actions_with_info.items():
activation_type = action_info.get("activation_type", ActionActivationType.ALWAYS)
if activation_type == ActionActivationType.ALWAYS:
always_actions[action_name] = action_info
elif activation_type == ActionActivationType.RANDOM:
random_actions[action_name] = action_info
elif activation_type == ActionActivationType.LLM_JUDGE:
llm_judge_actions[action_name] = action_info
elif activation_type == ActionActivationType.KEYWORD:
keyword_actions[action_name] = action_info
else:
logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理")
# 1. 处理ALWAYS类型直接激活
for action_name, action_info in always_actions.items():
activated_actions[action_name] = action_info
logger.debug(f"{self.log_prefix}激活动作: {action_name},原因: ALWAYS类型直接激活")
# 2. 处理RANDOM类型
for action_name, action_info in random_actions.items():
probability = action_info.get("random_probability", 0.3)
should_activate = random.random() < probability
if should_activate:
activated_actions[action_name] = action_info
logger.debug(f"{self.log_prefix}激活动作: {action_name},原因: RANDOM类型触发概率{probability}")
else:
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: RANDOM类型未触发概率{probability}")
# 3. 处理KEYWORD类型快速判定
for action_name, action_info in keyword_actions.items():
should_activate = self._check_keyword_activation(
action_name,
action_info,
chat_content,
)
if should_activate:
activated_actions[action_name] = action_info
keywords = action_info.get("activation_keywords", [])
logger.debug(f"{self.log_prefix}激活动作: {action_name},原因: KEYWORD类型匹配关键词{keywords}")
else:
keywords = action_info.get("activation_keywords", [])
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: KEYWORD类型未匹配关键词{keywords}")
# 4. 处理LLM_JUDGE类型并行判定
if llm_judge_actions:
# 直接并行处理所有LLM判定actions
llm_results = await self._process_llm_judge_actions_parallel(
llm_judge_actions,
chat_content,
)
# 添加激活的LLM判定actions
for action_name, should_activate in llm_results.items():
if should_activate:
activated_actions[action_name] = llm_judge_actions[action_name]
logger.debug(f"{self.log_prefix}激活动作: {action_name},原因: LLM_JUDGE类型判定通过")
else:
logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: LLM_JUDGE类型判定未通过")
logger.debug(f"{self.log_prefix}激活类型过滤完成: {list(activated_actions.keys())}")
return activated_actions
async def process_actions_for_planner(
self,
observed_messages_str: str = "",
chat_context: Optional[str] = None,
extra_context: Optional[str] = None
) -> Dict[str, Any]:
"""
[已废弃] 此方法现在已被整合到 modify_actions() 中
为了保持向后兼容性而保留,但建议直接使用 ActionManager.get_using_actions()
规划器应该直接从 ActionManager 获取最终的可用动作集,而不是调用此方法
新的架构:
1. 主循环调用 modify_actions() 处理完整的动作管理流程
2. 规划器直接使用 ActionManager.get_using_actions() 获取最终动作集
"""
logger.warning(f"{self.log_prefix}process_actions_for_planner() 已废弃,建议规划器直接使用 ActionManager.get_using_actions()")
# 为了向后兼容,仍然返回当前使用的动作集
current_using_actions = self.action_manager.get_using_actions()
all_registered_actions = self.action_manager.get_registered_actions()
# 构建完整的动作信息
result = {}
for action_name in current_using_actions.keys():
if action_name in all_registered_actions:
result[action_name] = all_registered_actions[action_name]
return result
def _generate_context_hash(self, chat_content: str) -> str:
"""生成上下文的哈希值用于缓存"""
context_content = f"{chat_content}"
return hashlib.md5(context_content.encode('utf-8')).hexdigest()
async def _process_llm_judge_actions_parallel(
self,
llm_judge_actions: Dict[str, Any],
chat_content: str = "",
) -> Dict[str, bool]:
"""
并行处理LLM判定actions支持智能缓存
Args:
llm_judge_actions: 需要LLM判定的actions
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文
extra_context: 额外上下文
Returns:
Dict[str, bool]: action名称到激活结果的映射
"""
# 生成当前上下文的哈希值
current_context_hash = self._generate_context_hash(chat_content)
current_time = time.time()
results = {}
tasks_to_run = {}
# 检查缓存
for action_name, action_info in llm_judge_actions.items():
cache_key = f"{action_name}_{current_context_hash}"
# 检查是否有有效的缓存
if (cache_key in self._llm_judge_cache and
current_time - self._llm_judge_cache[cache_key]["timestamp"] < self._cache_expiry_time):
results[action_name] = self._llm_judge_cache[cache_key]["result"]
logger.debug(f"{self.log_prefix}使用缓存结果 {action_name}: {'激活' if results[action_name] else '未激活'}")
else:
# 需要进行LLM判定
tasks_to_run[action_name] = action_info
# 如果有需要运行的任务,并行执行
if tasks_to_run:
logger.debug(f"{self.log_prefix}并行执行LLM判定任务数: {len(tasks_to_run)}")
# 创建并行任务
tasks = []
task_names = []
for action_name, action_info in tasks_to_run.items():
task = self._llm_judge_action(
action_name,
action_info,
chat_content,
)
tasks.append(task)
task_names.append(action_name)
# 并行执行所有任务
try:
task_results = await asyncio.gather(*tasks, return_exceptions=True)
# 处理结果并更新缓存
for i, (action_name, result) in enumerate(zip(task_names, task_results)):
if isinstance(result, Exception):
logger.error(f"{self.log_prefix}LLM判定action {action_name} 时出错: {result}")
results[action_name] = False
else:
results[action_name] = result
# 更新缓存
cache_key = f"{action_name}_{current_context_hash}"
self._llm_judge_cache[cache_key] = {
"result": result,
"timestamp": current_time
}
logger.debug(f"{self.log_prefix}并行LLM判定完成耗时: {time.time() - current_time:.2f}s")
except Exception as e:
logger.error(f"{self.log_prefix}并行LLM判定失败: {e}")
# 如果并行执行失败为所有任务返回False
for action_name in tasks_to_run.keys():
results[action_name] = False
# 清理过期缓存
self._cleanup_expired_cache(current_time)
return results
def _cleanup_expired_cache(self, current_time: float):
"""清理过期的缓存条目"""
expired_keys = []
for cache_key, cache_data in self._llm_judge_cache.items():
if current_time - cache_data["timestamp"] > self._cache_expiry_time:
expired_keys.append(cache_key)
for key in expired_keys:
del self._llm_judge_cache[key]
if expired_keys:
logger.debug(f"{self.log_prefix}清理了 {len(expired_keys)} 个过期缓存条目")
async def _llm_judge_action(
self,
action_name: str,
action_info: Dict[str, Any],
chat_content: str = "",
) -> bool:
"""
使用LLM判定是否应该激活某个action
Args:
action_name: 动作名称
action_info: 动作信息
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文
extra_context: 额外上下文
Returns:
bool: 是否应该激活此action
"""
try:
# 构建判定提示词
action_description = action_info.get("description", "")
action_require = action_info.get("require", [])
custom_prompt = action_info.get("llm_judge_prompt", "")
# 构建基础判定提示词
base_prompt = f"""
你需要判断在当前聊天情况下,是否应该激活名为"{action_name}"的动作。
动作描述:{action_description}
动作使用场景:
"""
for req in action_require:
base_prompt += f"- {req}\n"
if custom_prompt:
base_prompt += f"\n额外判定条件:\n{custom_prompt}\n"
if chat_content:
base_prompt += f"\n当前聊天记录:\n{chat_content}\n"
base_prompt += """
请根据以上信息判断是否应该激活这个动作。
只需要回答"""",不要有其他内容。
"""
# 调用LLM进行判定
response, _ = await self.llm_judge.generate_response_async(prompt=base_prompt)
# 解析响应
response = response.strip().lower()
print(base_prompt)
print(f"LLM判定动作 {action_name}:响应='{response}'")
should_activate = "" in response or "yes" in response or "true" in response
logger.debug(f"{self.log_prefix}LLM判定动作 {action_name}:响应='{response}',结果={'激活' if should_activate else '不激活'}")
return should_activate
except Exception as e:
logger.error(f"{self.log_prefix}LLM判定动作 {action_name} 时出错: {e}")
# 出错时默认不激活
return False
def _check_keyword_activation(
self,
action_name: str,
action_info: Dict[str, Any],
chat_content: str = "",
) -> bool:
"""
检查是否匹配关键词触发条件
Args:
action_name: 动作名称
action_info: 动作信息
observed_messages_str: 观察到的聊天消息
chat_context: 聊天上下文
extra_context: 额外上下文
Returns:
bool: 是否应该激活此action
"""
activation_keywords = action_info.get("activation_keywords", [])
case_sensitive = action_info.get("keyword_case_sensitive", False)
if not activation_keywords:
logger.warning(f"{self.log_prefix}动作 {action_name} 设置为关键词触发但未配置关键词")
return False
# 构建检索文本
search_text = ""
if chat_content:
search_text += chat_content
# if chat_context:
# search_text += f" {chat_context}"
# if extra_context:
# search_text += f" {extra_context}"
# 如果不区分大小写,转换为小写
if not case_sensitive:
search_text = search_text.lower()
# 检查每个关键词
matched_keywords = []
for keyword in activation_keywords:
check_keyword = keyword if case_sensitive else keyword.lower()
if check_keyword in search_text:
matched_keywords.append(keyword)
if matched_keywords:
logger.debug(f"{self.log_prefix}动作 {action_name} 匹配到关键词: {matched_keywords}")
return True
else:
logger.debug(f"{self.log_prefix}动作 {action_name} 未匹配到任何关键词: {activation_keywords}")
return False
async def analyze_loop_actions(self, obs: HFCloopObservation) -> Dict[str, List[str]]:
"""分析最近的循环内容并决定动作的增减
@@ -129,8 +558,6 @@ class ActionModifier:
reply_sequence.append(action_type == "reply")
# 检查no_reply比例
# print(f"no_reply_count: {no_reply_count}, len(recent_cycles): {len(recent_cycles)}")
# print(1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111)
if len(recent_cycles) >= (5 * global_config.chat.exit_focus_threshold) and (
no_reply_count / len(recent_cycles)
) >= (0.8 * global_config.chat.exit_focus_threshold):
@@ -138,6 +565,8 @@ class ActionModifier:
result["add"].append("exit_focus_chat")
result["remove"].append("no_reply")
result["remove"].append("reply")
no_reply_ratio = no_reply_count / len(recent_cycles)
logger.info(f"{self.log_prefix}检测到高no_reply比例: {no_reply_ratio:.2f}达到退出聊天阈值将添加exit_focus_chat并移除no_reply/reply动作")
# 计算连续回复的相关阈值
@@ -162,34 +591,37 @@ class ActionModifier:
if len(last_max_reply_num) >= max_reply_num and all(last_max_reply_num):
# 如果最近max_reply_num次都是reply直接移除
result["remove"].append("reply")
reply_count = len(last_max_reply_num) - no_reply_count
logger.info(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply直接移除"
f"{self.log_prefix}移除reply动作,原因: 连续回复过多(最近{len(last_max_reply_num)}次全是reply超过阈值{max_reply_num}"
)
elif len(last_max_reply_num) >= sec_thres_reply_num and all(last_max_reply_num[-sec_thres_reply_num:]):
# 如果最近sec_thres_reply_num次都是reply40%概率移除
if random.random() < 0.4 / global_config.focus_chat.consecutive_replies:
removal_probability = 0.4 / global_config.focus_chat.consecutive_replies
if random.random() < removal_probability:
result["remove"].append("reply")
logger.info(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.4 / global_config.focus_chat.consecutive_replies}概率移除,移除"
f"{self.log_prefix}移除reply动作,原因: 连续回复较多(最近{sec_thres_reply_num}全是reply{removal_probability:.2f}概率移除,触发移除"
)
else:
logger.debug(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.4 / global_config.focus_chat.consecutive_replies}概率移除,不移除"
f"{self.log_prefix}连续回复检测:最近{sec_thres_reply_num}全是reply{removal_probability:.2f}概率移除,未触发"
)
elif len(last_max_reply_num) >= one_thres_reply_num and all(last_max_reply_num[-one_thres_reply_num:]):
# 如果最近one_thres_reply_num次都是reply20%概率移除
if random.random() < 0.2 / global_config.focus_chat.consecutive_replies:
removal_probability = 0.2 / global_config.focus_chat.consecutive_replies
if random.random() < removal_probability:
result["remove"].append("reply")
logger.info(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.2 / global_config.focus_chat.consecutive_replies}概率移除,移除"
f"{self.log_prefix}移除reply动作,原因: 连续回复检测(最近{one_thres_reply_num}全是reply{removal_probability:.2f}概率移除,触发移除"
)
else:
logger.debug(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply{0.2 / global_config.focus_chat.consecutive_replies}概率移除,不移除"
f"{self.log_prefix}连续回复检测:最近{one_thres_reply_num}全是reply{removal_probability:.2f}概率移除,未触发"
)
else:
logger.debug(
f"最近{len(last_max_reply_num)}次回复中,有{no_reply_count}次no_reply{len(last_max_reply_num) - no_reply_count}次reply无需移除"
f"{self.log_prefix}连续回复检测无需移除reply动作最近回复模式正常"
)
return result

View File

@@ -15,6 +15,7 @@ from src.common.logger_manager import get_logger
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.individuality.individuality import individuality
from src.chat.focus_chat.planners.action_manager import ActionManager
from src.chat.focus_chat.planners.modify_actions import ActionModifier
from json_repair import repair_json
from src.chat.focus_chat.planners.base_planner import BasePlanner
from datetime import datetime
@@ -141,8 +142,18 @@ class ActionPlanner(BasePlanner):
# elif not isinstance(info, ActionInfo): # 跳过已处理的ActionInfo
# extra_info.append(info.get_processed_info())
# 获取当前可用动作
current_available_actions = self.action_manager.get_using_actions()
# 获取经过modify_actions处理后的最终可用动作
# 注意动作的激活判定现在在主循环的modify_actions中完成
current_available_actions_dict = self.action_manager.get_using_actions()
# 获取完整的动作信息
all_registered_actions = self.action_manager.get_registered_actions()
current_available_actions = {}
for action_name in current_available_actions_dict.keys():
if action_name in all_registered_actions:
current_available_actions[action_name] = all_registered_actions[action_name]
else:
logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到")
# 如果没有可用动作或只有no_reply动作直接返回no_reply
if not current_available_actions or (