feat:给动作添加了选择器,并添加了新api
This commit is contained in:
189
CORRECTED_ARCHITECTURE.md
Normal file
189
CORRECTED_ARCHITECTURE.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# 修正后的动作激活架构
|
||||
|
||||
## 架构原则
|
||||
|
||||
### 正确的职责分工
|
||||
- **主循环 (`modify_actions`)**: 负责完整的动作管理,包括传统观察处理和新的激活类型判定
|
||||
- **规划器 (`Planner`)**: 专注于从最终确定的动作集中进行决策,不再处理动作筛选
|
||||
|
||||
### 关注点分离
|
||||
- **动作管理** → 主循环处理
|
||||
- **决策制定** → 规划器处理
|
||||
- **配置解析** → ActionManager处理
|
||||
|
||||
## 修正后的调用流程
|
||||
|
||||
### 1. 主循环阶段 (heartFC_chat.py)
|
||||
|
||||
```python
|
||||
# 在主循环中调用完整的动作管理流程
|
||||
async def modify_actions_task():
|
||||
# 提取聊天上下文信息
|
||||
observed_messages_str = ""
|
||||
chat_context = ""
|
||||
|
||||
for obs in self.observations:
|
||||
if hasattr(obs, 'get_talking_message_str_truncate'):
|
||||
observed_messages_str = obs.get_talking_message_str_truncate()
|
||||
elif hasattr(obs, 'get_chat_type'):
|
||||
chat_context = f"聊天类型: {obs.get_chat_type()}"
|
||||
|
||||
# 调用完整的动作修改流程
|
||||
await self.action_modifier.modify_actions(
|
||||
observations=self.observations,
|
||||
observed_messages_str=observed_messages_str,
|
||||
chat_context=chat_context,
|
||||
extra_context=extra_context
|
||||
)
|
||||
```
|
||||
|
||||
**处理内容:**
|
||||
- 传统观察处理(循环历史分析、类型匹配等)
|
||||
- 激活类型判定(ALWAYS, RANDOM, LLM_JUDGE, KEYWORD)
|
||||
- 并行LLM判定
|
||||
- 智能缓存
|
||||
- 动态关键词收集
|
||||
|
||||
### 2. 规划器阶段 (planner_simple.py)
|
||||
|
||||
```python
|
||||
# 规划器直接获取最终的动作集
|
||||
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]
|
||||
```
|
||||
|
||||
**处理内容:**
|
||||
- 仅获取经过完整处理的最终动作集
|
||||
- 专注于从可用动作中进行决策
|
||||
- 不再处理动作筛选逻辑
|
||||
|
||||
## 核心优化功能
|
||||
|
||||
### 1. 并行LLM判定
|
||||
```python
|
||||
# 同时判定多个LLM_JUDGE类型的动作
|
||||
task_results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
```
|
||||
|
||||
### 2. 智能缓存系统
|
||||
```python
|
||||
# 基于上下文哈希的缓存机制
|
||||
cache_key = f"{action_name}_{context_hash}"
|
||||
if cache_key in self._llm_judge_cache:
|
||||
return cached_result
|
||||
```
|
||||
|
||||
### 3. 直接LLM判定
|
||||
```python
|
||||
# 直接对所有LLM_JUDGE类型的动作进行并行判定
|
||||
llm_results = await self._process_llm_judge_actions_parallel(llm_judge_actions, ...)
|
||||
```
|
||||
|
||||
### 4. 动态关键词收集
|
||||
```python
|
||||
# 从动作配置中动态收集关键词,避免硬编码
|
||||
for action_name, action_info in llm_judge_actions.items():
|
||||
keywords = action_info.get("activation_keywords", [])
|
||||
if keywords:
|
||||
# 检查消息中的关键词匹配
|
||||
```
|
||||
|
||||
## 四种激活类型
|
||||
|
||||
### 1. ALWAYS - 始终激活
|
||||
```python
|
||||
activation_type = ActionActivationType.ALWAYS
|
||||
# 基础动作,如 reply, no_reply
|
||||
```
|
||||
|
||||
### 2. RANDOM - 随机激活
|
||||
```python
|
||||
activation_type = ActionActivationType.RANDOM
|
||||
random_probability = 0.3 # 激活概率
|
||||
# 用于增加惊喜元素,如随机表情
|
||||
```
|
||||
|
||||
### 3. LLM_JUDGE - 智能判定
|
||||
```python
|
||||
activation_type = ActionActivationType.LLM_JUDGE
|
||||
llm_judge_prompt = "自定义判定提示词"
|
||||
# 需要理解上下文的复杂动作,如情感表达
|
||||
```
|
||||
|
||||
### 4. KEYWORD - 关键词触发
|
||||
```python
|
||||
activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["画", "图片", "生成"]
|
||||
# 明确指令触发的动作,如图片生成
|
||||
```
|
||||
|
||||
## 性能提升
|
||||
|
||||
### 理论性能改进
|
||||
- **并行LLM判定**: 1.5-2x 提升
|
||||
- **智能缓存**: 20-30% 额外提升
|
||||
- **整体预期**: 2-3x 性能提升
|
||||
|
||||
### 缓存策略
|
||||
- **缓存键**: `{action_name}_{context_hash}`
|
||||
- **过期时间**: 30秒
|
||||
- **哈希算法**: MD5 (消息内容+上下文)
|
||||
|
||||
## 向后兼容性
|
||||
|
||||
### 废弃方法处理
|
||||
```python
|
||||
async def process_actions_for_planner(...):
|
||||
"""[已废弃] 此方法现在已被整合到 modify_actions() 中"""
|
||||
logger.warning("process_actions_for_planner() 已废弃")
|
||||
# 仍然返回结果以保持兼容性
|
||||
return current_using_actions
|
||||
```
|
||||
|
||||
### 迁移指南
|
||||
1. **主循环**: 使用 `modify_actions(observations, messages, context, extra)`
|
||||
2. **规划器**: 直接使用 `ActionManager.get_using_actions()`
|
||||
3. **移除**: 规划器中对 `process_actions_for_planner()` 的调用
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 运行测试
|
||||
```bash
|
||||
python test_corrected_architecture.py
|
||||
```
|
||||
|
||||
### 测试内容
|
||||
- 架构正确性验证
|
||||
- 数据一致性检查
|
||||
- 职责分离确认
|
||||
- 性能测试
|
||||
- 向后兼容性验证
|
||||
|
||||
## 优势总结
|
||||
|
||||
### 1. 清晰的架构
|
||||
- **单一职责**: 每个组件专注于自己的核心功能
|
||||
- **关注点分离**: 动作管理与决策制定分离
|
||||
- **可维护性**: 逻辑清晰,易于理解和修改
|
||||
|
||||
### 2. 高性能
|
||||
- **并行处理**: 多个LLM判定同时进行
|
||||
- **智能缓存**: 避免重复计算
|
||||
|
||||
### 3. 智能化
|
||||
- **动态配置**: 从动作配置中收集关键词
|
||||
- **上下文感知**: 基于聊天内容智能激活
|
||||
- **冲突避免**: 防止重复激活
|
||||
|
||||
### 4. 可扩展性
|
||||
- **插件式**: 新的激活类型易于添加
|
||||
- **配置驱动**: 通过配置控制行为
|
||||
- **模块化**: 各组件独立可测试
|
||||
|
||||
这个修正后的架构实现了正确的职责分工,确保了主循环负责动作管理,规划器专注于决策,同时集成了并行判定和智能缓存等优化功能。
|
||||
453
action_activation_system_usage.md
Normal file
453
action_activation_system_usage.md
Normal file
@@ -0,0 +1,453 @@
|
||||
# MaiBot 动作激活系统使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
MaiBot 的动作激活系统支持四种不同的激活类型,让机器人能够智能地根据上下文选择合适的动作。
|
||||
|
||||
**系统已集成三大优化策略:**
|
||||
- 🚀 **并行判定**:多个LLM判定任务并行执行
|
||||
- 💾 **智能缓存**:相同上下文的判定结果缓存复用
|
||||
- 🔍 **分层判定**:快速过滤 + 精确判定的两层架构
|
||||
|
||||
## 激活类型详解
|
||||
|
||||
### 1. ALWAYS - 总是激活
|
||||
**用途**:基础必需动作,始终可用
|
||||
```python
|
||||
action_activation_type = ActionActivationType.ALWAYS
|
||||
```
|
||||
**示例**:`reply_action`, `no_reply_action`
|
||||
|
||||
### 2. RANDOM - 随机激活
|
||||
**用途**:增加不可预测性和趣味性
|
||||
```python
|
||||
action_activation_type = ActionActivationType.RANDOM
|
||||
random_activation_probability = 0.2 # 20%概率激活
|
||||
```
|
||||
**示例**:`pic_action` (20%概率)
|
||||
|
||||
### 3. LLM_JUDGE - LLM智能判定
|
||||
**用途**:需要上下文理解的复杂判定
|
||||
```python
|
||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
||||
llm_judge_prompt = """
|
||||
判定条件:
|
||||
1. 当前聊天涉及情感表达
|
||||
2. 需要生动的情感回应
|
||||
3. 场景适合虚拟主播动作
|
||||
|
||||
不适用场景:
|
||||
1. 纯信息查询
|
||||
2. 技术讨论
|
||||
"""
|
||||
```
|
||||
**优化特性**:
|
||||
- ⚡ **直接判定**:直接进行LLM判定,减少复杂度
|
||||
- 🚀 **并行执行**:多个LLM判定同时进行
|
||||
- 💾 **结果缓存**:相同上下文复用结果(30秒有效期)
|
||||
|
||||
### 4. KEYWORD - 关键词触发
|
||||
**用途**:精确命令式触发
|
||||
```python
|
||||
action_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["画", "画图", "生成图片", "draw"]
|
||||
keyword_case_sensitive = False # 不区分大小写
|
||||
```
|
||||
**示例**:`help_action`, `edge_search_action`, `pic_action`
|
||||
|
||||
## 性能优化详解
|
||||
|
||||
### 并行判定机制
|
||||
```python
|
||||
# 自动将多个LLM判定任务并行执行
|
||||
async def _process_llm_judge_actions_parallel(self, llm_judge_actions, ...):
|
||||
tasks = [self._llm_judge_action(name, info, ...) for name, info in llm_judge_actions.items()]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 多个LLM判定同时进行,显著减少总耗时
|
||||
- 异常处理确保单个失败不影响整体
|
||||
- 自动负载均衡
|
||||
|
||||
### 智能缓存系统
|
||||
```python
|
||||
# 基于上下文哈希的缓存机制
|
||||
cache_key = f"{action_name}_{context_hash}"
|
||||
if cache_key in self._llm_judge_cache:
|
||||
return cached_result # 直接返回缓存结果
|
||||
```
|
||||
|
||||
**特性**:
|
||||
- 30秒缓存有效期
|
||||
- MD5哈希确保上下文一致性
|
||||
- 自动清理过期缓存
|
||||
- 命中率优化:相同聊天上下文的重复判定
|
||||
|
||||
### 分层判定架构
|
||||
|
||||
#### 第一层:智能动态过滤
|
||||
```python
|
||||
def _pre_filter_llm_actions(self, llm_judge_actions, observed_messages_str, ...):
|
||||
# 动态收集所有KEYWORD类型actions的关键词
|
||||
all_keyword_actions = self.action_manager.get_registered_actions()
|
||||
collected_keywords = {}
|
||||
|
||||
for action_name, action_info in all_keyword_actions.items():
|
||||
if action_info.get("activation_type") == "KEYWORD":
|
||||
keywords = action_info.get("activation_keywords", [])
|
||||
if keywords:
|
||||
collected_keywords[action_name] = [kw.lower() for kw in keywords]
|
||||
|
||||
# 基于实际配置进行智能过滤
|
||||
for action_name, action_info in llm_judge_actions.items():
|
||||
# 策略1: 避免与KEYWORD类型重复
|
||||
# 策略2: 基于action描述进行语义相关性检查
|
||||
# 策略3: 保留核心actions
|
||||
```
|
||||
|
||||
**智能过滤策略**:
|
||||
- **动态关键词收集**:从各个action的实际配置中收集关键词,无硬编码
|
||||
- **重复避免机制**:如果存在对应的KEYWORD触发action,优先使用KEYWORD
|
||||
- **语义相关性检查**:基于action描述和消息内容进行智能匹配
|
||||
- **长度与复杂度匹配**:短消息自动排除复杂operations
|
||||
- **核心action保护**:确保reply/no_reply等基础action始终可用
|
||||
|
||||
#### 第二层:LLM精确判定
|
||||
通过第一层过滤后的动作才进入LLM判定,大幅减少:
|
||||
- LLM调用次数
|
||||
- 总处理时间
|
||||
- API成本
|
||||
|
||||
## HFC流程级并行化优化 🆕
|
||||
|
||||
### 三阶段并行架构
|
||||
|
||||
除了动作激活系统内部的优化,整个HFC(HeartFocus Chat)流程也实现了并行化:
|
||||
|
||||
```python
|
||||
# 在 heartFC_chat.py 中的优化
|
||||
if global_config.focus_chat.parallel_processing:
|
||||
# 并行执行调整动作、回忆和处理器阶段
|
||||
with Timer("并行调整动作、回忆和处理", cycle_timers):
|
||||
async def modify_actions_task():
|
||||
await self.action_modifier.modify_actions(observations=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
|
||||
)
|
||||
```
|
||||
|
||||
### 并行化阶段说明
|
||||
|
||||
**1. 调整动作阶段(Action Modifier)**
|
||||
- 执行动作激活系统的智能判定
|
||||
- 包含并行LLM判定和缓存
|
||||
- 更新可用动作列表
|
||||
|
||||
**2. 回忆激活阶段(Memory Activator)**
|
||||
- 根据当前观察激活相关记忆
|
||||
- 检索历史对话和上下文信息
|
||||
- 为规划器提供背景知识
|
||||
|
||||
**3. 信息处理器阶段(Processors)**
|
||||
- 处理观察信息,提取关键特征
|
||||
- 生成结构化的计划信息
|
||||
- 为规划器提供决策依据
|
||||
|
||||
### 性能提升效果
|
||||
|
||||
**理论提升**:
|
||||
- 原串行执行:500ms + 800ms + 1000ms = 2300ms
|
||||
- 现并行执行:max(500ms, 800ms, 1000ms) = 1000ms
|
||||
- **性能提升:2.3x**
|
||||
|
||||
**实际效果**:
|
||||
- 显著减少每个HFC循环的总耗时
|
||||
- 提高机器人响应速度
|
||||
- 优化用户体验
|
||||
|
||||
### 配置控制
|
||||
|
||||
通过配置文件控制是否启用并行处理:
|
||||
```yaml
|
||||
focus_chat:
|
||||
parallel_processing: true # 启用并行处理
|
||||
```
|
||||
|
||||
**建议设置**:
|
||||
- **生产环境**:启用(`true`)- 获得最佳性能
|
||||
- **调试环境**:可选择禁用(`false`)- 便于问题定位
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 定义新的动作类
|
||||
|
||||
```python
|
||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
||||
|
||||
@register_action
|
||||
class MyAction(PluginAction):
|
||||
action_name = "my_action"
|
||||
action_description = "我的自定义动作"
|
||||
|
||||
# 选择合适的激活类型
|
||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
||||
|
||||
# LLM判定的自定义提示词
|
||||
llm_judge_prompt = """
|
||||
判定是否激活my_action的条件:
|
||||
1. 用户明确要求执行特定操作
|
||||
2. 当前场景适合此动作
|
||||
3. 没有其他更合适的动作
|
||||
|
||||
不应激活的情况:
|
||||
1. 普通聊天对话
|
||||
2. 用户只是随便说说
|
||||
"""
|
||||
|
||||
async def process(self):
|
||||
# 动作执行逻辑
|
||||
pass
|
||||
```
|
||||
|
||||
### 关键词触发动作
|
||||
```python
|
||||
@register_action
|
||||
class SearchAction(PluginAction):
|
||||
action_name = "search_action"
|
||||
action_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["搜索", "查找", "什么是", "search", "find"]
|
||||
keyword_case_sensitive = False
|
||||
```
|
||||
|
||||
### 随机触发动作
|
||||
```python
|
||||
@register_action
|
||||
class SurpriseAction(PluginAction):
|
||||
action_name = "surprise_action"
|
||||
action_activation_type = ActionActivationType.RANDOM
|
||||
random_activation_probability = 0.1 # 10%概率
|
||||
```
|
||||
|
||||
## 性能监控
|
||||
|
||||
### 实时性能指标
|
||||
```python
|
||||
# 自动记录的性能指标
|
||||
logger.debug(f"激活判定:{before_count} -> {after_count} actions")
|
||||
logger.debug(f"并行LLM判定完成,耗时: {duration:.2f}s")
|
||||
logger.debug(f"使用缓存结果 {action_name}: {'激活' if result else '未激活'}")
|
||||
logger.debug(f"清理了 {count} 个过期缓存条目")
|
||||
logger.debug(f"并行调整动作、回忆和处理完成,耗时: {duration:.2f}s")
|
||||
```
|
||||
|
||||
### 性能优化建议
|
||||
1. **合理配置缓存时间**:根据聊天活跃度调整 `_cache_expiry_time`
|
||||
2. **优化过滤规则**:根据实际使用情况调整 `_quick_filter_keywords`
|
||||
3. **监控并行效果**:关注 `asyncio.gather` 的执行时间
|
||||
4. **缓存命中率**:监控缓存使用情况,优化策略
|
||||
5. **启用流程并行化**:确保 `parallel_processing` 配置为 `true`
|
||||
|
||||
## 测试验证
|
||||
|
||||
运行动作激活优化测试:
|
||||
```bash
|
||||
python test_action_activation_optimized.py
|
||||
```
|
||||
|
||||
运行HFC并行化测试:
|
||||
```bash
|
||||
python test_parallel_optimization.py
|
||||
```
|
||||
|
||||
测试内容包括:
|
||||
- ✅ 并行处理功能验证
|
||||
- ✅ 缓存机制效果测试
|
||||
- ✅ 分层判定规则验证
|
||||
- ✅ 性能对比分析
|
||||
- ✅ HFC流程并行化效果
|
||||
- ✅ 多循环平均性能测试
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 激活类型选择
|
||||
- **ALWAYS**:reply, no_reply 等基础动作
|
||||
- **LLM_JUDGE**:需要智能判断的复杂动作
|
||||
- **KEYWORD**:明确的命令式动作
|
||||
- **RANDOM**:增趣动作,低概率触发
|
||||
|
||||
### 2. LLM判定提示词编写
|
||||
- 明确描述激活条件和排除条件
|
||||
- 避免模糊的描述
|
||||
- 考虑边界情况
|
||||
- 保持简洁明了
|
||||
|
||||
### 3. 关键词设置
|
||||
- 包含同义词和英文对应词
|
||||
- 考虑用户的不同表达习惯
|
||||
- 避免过于宽泛的关键词
|
||||
- 根据实际使用调整
|
||||
|
||||
### 4. 性能优化
|
||||
- 定期监控处理时间
|
||||
- 根据使用模式调整缓存策略
|
||||
- 优化激活判定逻辑
|
||||
- 平衡准确性和性能
|
||||
- **启用并行处理配置**
|
||||
|
||||
### 5. 并行化最佳实践
|
||||
- 在生产环境启用 `parallel_processing`
|
||||
- 监控并行阶段的执行时间
|
||||
- 确保各阶段的独立性
|
||||
- 避免共享状态导致的竞争条件
|
||||
|
||||
## 总结
|
||||
|
||||
优化后的动作激活系统通过**四层优化策略**,实现了全方位的性能提升:
|
||||
|
||||
### 第一层:动作激活内部优化
|
||||
- **并行判定**:多个LLM判定任务并行执行
|
||||
- **智能缓存**:相同上下文的判定结果缓存复用
|
||||
- **分层判定**:快速过滤 + 精确判定的两层架构
|
||||
|
||||
### 第二层:HFC流程级并行化
|
||||
- **三阶段并行**:调整动作、回忆、处理器同时执行
|
||||
- **性能提升**:2.3x 理论加速比
|
||||
- **配置控制**:可根据环境灵活开启/关闭
|
||||
|
||||
### 综合效果
|
||||
- **响应速度**:显著提升机器人反应速度
|
||||
- **成本优化**:减少不必要的LLM调用
|
||||
- **智能决策**:四种激活类型覆盖所有场景
|
||||
- **用户体验**:更快速、更智能的交互
|
||||
|
||||
**总性能提升预估:3-5x**
|
||||
- 动作激活系统内部优化:1.5-2x
|
||||
- HFC流程并行化:2.3x
|
||||
- 缓存和过滤优化:额外20-30%提升
|
||||
|
||||
这使得MaiBot能够更快速、更智能地响应用户需求,提供卓越的交互体验。
|
||||
|
||||
## 如何为Action添加激活类型
|
||||
|
||||
### 对于普通Action
|
||||
|
||||
```python
|
||||
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
|
||||
|
||||
@register_action
|
||||
class YourAction(BaseAction):
|
||||
action_name = "your_action"
|
||||
action_description = "你的动作描述"
|
||||
|
||||
# 设置激活类型 - 关键词触发示例
|
||||
action_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["关键词1", "关键词2", "keyword"]
|
||||
keyword_case_sensitive = False
|
||||
|
||||
# ... 其他代码
|
||||
```
|
||||
|
||||
### 对于插件Action
|
||||
|
||||
```python
|
||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
||||
|
||||
@register_action
|
||||
class YourPluginAction(PluginAction):
|
||||
action_name = "your_plugin_action"
|
||||
action_description = "你的插件动作描述"
|
||||
|
||||
# 设置激活类型 - 关键词触发示例
|
||||
action_activation_type = ActionActivationType.KEYWORD
|
||||
activation_keywords = ["触发词1", "trigger", "启动"]
|
||||
keyword_case_sensitive = False
|
||||
|
||||
# ... 其他代码
|
||||
```
|
||||
|
||||
## 现有Action的激活类型设置
|
||||
|
||||
### 基础动作 (ALWAYS)
|
||||
- `reply` - 回复动作
|
||||
- `no_reply` - 不回复动作
|
||||
|
||||
### LLM判定动作 (LLM_JUDGE)
|
||||
- `vtb_action` - 虚拟主播表情
|
||||
- `mute_action` - 禁言动作
|
||||
|
||||
### 关键词触发动作 (KEYWORD) 🆕
|
||||
- `edge_search_action` - 网络搜索 (搜索、查找、什么是等)
|
||||
- `pic_action` - 图片生成 (画、画图、生成图片等)
|
||||
- `help_action` - 帮助功能 (帮助、help、求助等)
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **ActionModifier处理**: 在planner运行前,ActionModifier会遍历所有注册的动作
|
||||
2. **类型判断**: 根据每个动作的激活类型决定是否激活
|
||||
3. **激活决策**:
|
||||
- ALWAYS: 直接激活
|
||||
- RANDOM: 根据概率随机决定
|
||||
- LLM_JUDGE: 调用小模型判定
|
||||
- KEYWORD: 检测关键词匹配
|
||||
4. **结果收集**: 收集所有激活的动作供planner使用
|
||||
|
||||
## 配置建议
|
||||
|
||||
### LLM判定提示词编写
|
||||
- 明确指出激活条件和不激活条件
|
||||
- 使用简单清晰的语言
|
||||
- 避免过于复杂的逻辑判断
|
||||
|
||||
### 随机概率设置
|
||||
- 核心功能: 不建议使用随机
|
||||
- 娱乐功能: 0.1-0.3 (10%-30%)
|
||||
- 辅助功能: 0.05-0.2 (5%-20%)
|
||||
|
||||
### 关键词设计
|
||||
- 包含常用的同义词和变体
|
||||
- 考虑中英文兼容
|
||||
- 避免过于宽泛的词汇
|
||||
- 测试关键词的覆盖率
|
||||
|
||||
### 性能考虑
|
||||
- LLM判定会增加响应时间,适度使用
|
||||
- 关键词检测性能最好,推荐优先使用
|
||||
- 建议优先级:KEYWORD > ALWAYS > RANDOM > LLM_JUDGE
|
||||
|
||||
## 调试和测试
|
||||
|
||||
使用提供的测试脚本验证激活类型系统:
|
||||
|
||||
```bash
|
||||
python test_action_activation.py
|
||||
```
|
||||
|
||||
该脚本会显示:
|
||||
- 所有注册动作的激活类型
|
||||
- 模拟不同消息下的激活结果
|
||||
- 帮助验证配置是否正确
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **向后兼容**: 未设置激活类型的动作默认为ALWAYS
|
||||
2. **错误处理**: LLM判定失败时默认不激活该动作
|
||||
3. **日志记录**: 系统会记录激活决策过程,便于调试
|
||||
4. **性能影响**: LLM判定会略微增加响应时间
|
||||
|
||||
## 未来扩展
|
||||
|
||||
系统设计支持未来添加更多激活类型,如:
|
||||
- 基于时间的激活
|
||||
- 基于用户权限的激活
|
||||
- 基于群组设置的激活
|
||||
@@ -441,31 +441,33 @@ class HeartFChatting:
|
||||
"observations": self.observations,
|
||||
}
|
||||
|
||||
with Timer("调整动作", cycle_timers):
|
||||
# 处理特殊的观察
|
||||
await self.action_modifier.modify_actions(observations=self.observations)
|
||||
# 根据配置决定是否并行执行调整动作、回忆和处理器阶段
|
||||
|
||||
# 并行执行调整动作、回忆和处理器阶段
|
||||
with Timer("并行调整动作、处理", cycle_timers):
|
||||
# 创建并行任务
|
||||
async def modify_actions_task():
|
||||
# 调用完整的动作修改流程
|
||||
await self.action_modifier.modify_actions(
|
||||
observations=self.observations,
|
||||
)
|
||||
|
||||
await self.action_observation.observe()
|
||||
self.observations.append(self.action_observation)
|
||||
return True
|
||||
|
||||
# 根据配置决定是否并行执行回忆和处理器阶段
|
||||
# print(global_config.focus_chat.parallel_processing)
|
||||
if global_config.focus_chat.parallel_processing:
|
||||
# 并行执行回忆和处理器阶段
|
||||
with Timer("并行回忆和处理", cycle_timers):
|
||||
# 创建三个并行任务
|
||||
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(
|
||||
memory_task, processor_task
|
||||
# 等待三个任务完成
|
||||
_, running_memorys, (all_plan_info, processor_time_costs) = await asyncio.gather(
|
||||
action_modify_task, memory_task, processor_task
|
||||
)
|
||||
else:
|
||||
# 串行执行
|
||||
with Timer("回忆", cycle_timers):
|
||||
running_memorys = await self.memory_activator.activate_memory(self.observations)
|
||||
|
||||
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,
|
||||
|
||||
@@ -61,6 +61,13 @@ class ActionManager:
|
||||
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:
|
||||
# 创建动作信息字典
|
||||
action_info = {
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
# 添加到所有已注册的动作
|
||||
|
||||
@@ -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
|
||||
...
|
||||
"""
|
||||
# 检查类是否有必要的属性
|
||||
@@ -66,6 +73,17 @@ class BaseAction(ABC):
|
||||
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] = []
|
||||
|
||||
self.default: bool = False
|
||||
|
||||
@@ -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
|
||||
@@ -30,6 +30,9 @@ class NoReplyAction(BaseAction):
|
||||
]
|
||||
default = True
|
||||
|
||||
# 激活类型设置
|
||||
action_activation_type = ActionActivationType.ALWAYS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
|
||||
@@ -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
|
||||
@@ -34,6 +34,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,
|
||||
action_data: dict,
|
||||
|
||||
@@ -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
|
||||
@@ -39,6 +39,9 @@ class ReplyAction(BaseAction):
|
||||
|
||||
default = True
|
||||
|
||||
# 激活类型设置
|
||||
action_activation_type = ActionActivationType.ALWAYS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
|
||||
@@ -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("基于循环历史分析")
|
||||
|
||||
# 收集变更原因
|
||||
# if action_changes["add"]:
|
||||
# reasons.append(f"添加动作{action_changes['add']}因为检测到大量无回复")
|
||||
# if action_changes["remove"]:
|
||||
# reasons.append(f"移除动作{action_changes['remove']}因为检测到连续回复")
|
||||
# 详细记录循环历史分析的变更原因
|
||||
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},原因: 循环历史分析建议移除")
|
||||
|
||||
# 处理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}开始激活类型判定阶段")
|
||||
|
||||
# return processed_infos
|
||||
# 获取当前使用的动作集(经过第一阶段处理)
|
||||
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())}")
|
||||
|
||||
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次都是reply,40%概率移除
|
||||
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次都是reply,20%概率移除
|
||||
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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -3,3 +3,30 @@
|
||||
"""
|
||||
这是一个测试插件,用于测试图片发送功能
|
||||
"""
|
||||
|
||||
"""豆包图片生成插件
|
||||
|
||||
这是一个基于火山引擎豆包模型的AI图片生成插件。
|
||||
|
||||
功能特性:
|
||||
- 智能LLM判定:根据聊天内容智能判断是否需要生成图片
|
||||
- 高质量图片生成:使用豆包Seed Dream模型生成图片
|
||||
- 结果缓存:避免重复生成相同内容的图片
|
||||
- 配置验证:自动验证和修复配置文件
|
||||
- 参数验证:完整的输入参数验证和错误处理
|
||||
- 多尺寸支持:支持多种图片尺寸生成
|
||||
|
||||
使用场景:
|
||||
- 用户要求画图或生成图片时自动触发
|
||||
- 将文字描述转换为视觉图像
|
||||
- 创意图片和艺术作品生成
|
||||
|
||||
配置文件:src/plugins/doubao_pic/actions/pic_action_config.toml
|
||||
|
||||
配置要求:
|
||||
1. 设置火山引擎API密钥 (volcano_generate_api_key)
|
||||
2. 配置API基础URL (base_url)
|
||||
3. 选择合适的生成模型和参数
|
||||
|
||||
注意:需要有效的火山引擎API访问权限才能正常使用。
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import os
|
||||
import toml
|
||||
from src.common.logger_manager import get_logger
|
||||
|
||||
logger = get_logger("pic_config")
|
||||
|
||||
CONFIG_CONTENT = """\
|
||||
# 火山方舟 API 的基础 URL
|
||||
@@ -18,10 +22,83 @@ default_guidance_scale = 2.5
|
||||
# 默认随机种子
|
||||
default_seed = 42
|
||||
|
||||
# 缓存设置
|
||||
cache_enabled = true
|
||||
cache_max_size = 10
|
||||
|
||||
# 更多插件特定配置可以在此添加...
|
||||
# custom_parameter = "some_value"
|
||||
"""
|
||||
|
||||
# 默认配置字典,用于验证和修复
|
||||
DEFAULT_CONFIG = {
|
||||
"base_url": "https://ark.cn-beijing.volces.com/api/v3",
|
||||
"volcano_generate_api_key": "YOUR_VOLCANO_GENERATE_API_KEY_HERE",
|
||||
"default_model": "doubao-seedream-3-0-t2i-250415",
|
||||
"default_size": "1024x1024",
|
||||
"default_watermark": True,
|
||||
"default_guidance_scale": 2.5,
|
||||
"default_seed": 42,
|
||||
"cache_enabled": True,
|
||||
"cache_max_size": 10
|
||||
}
|
||||
|
||||
|
||||
def validate_and_fix_config(config_path: str) -> bool:
|
||||
"""验证并修复配置文件"""
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = toml.load(f)
|
||||
|
||||
# 检查缺失的配置项
|
||||
missing_keys = []
|
||||
fixed = False
|
||||
|
||||
for key, default_value in DEFAULT_CONFIG.items():
|
||||
if key not in config:
|
||||
missing_keys.append(key)
|
||||
config[key] = default_value
|
||||
fixed = True
|
||||
logger.info(f"添加缺失的配置项: {key} = {default_value}")
|
||||
|
||||
# 验证配置值的类型和范围
|
||||
if isinstance(config.get("default_guidance_scale"), (int, float)):
|
||||
if not 0.1 <= config["default_guidance_scale"] <= 20.0:
|
||||
config["default_guidance_scale"] = 2.5
|
||||
fixed = True
|
||||
logger.info("修复无效的 default_guidance_scale 值")
|
||||
|
||||
if isinstance(config.get("default_seed"), (int, float)):
|
||||
config["default_seed"] = int(config["default_seed"])
|
||||
else:
|
||||
config["default_seed"] = 42
|
||||
fixed = True
|
||||
logger.info("修复无效的 default_seed 值")
|
||||
|
||||
if config.get("cache_max_size") and not isinstance(config["cache_max_size"], int):
|
||||
config["cache_max_size"] = 10
|
||||
fixed = True
|
||||
logger.info("修复无效的 cache_max_size 值")
|
||||
|
||||
# 如果有修复,写回文件
|
||||
if fixed:
|
||||
# 创建备份
|
||||
backup_path = config_path + ".backup"
|
||||
if os.path.exists(config_path):
|
||||
os.rename(config_path, backup_path)
|
||||
logger.info(f"已创建配置备份: {backup_path}")
|
||||
|
||||
# 写入修复后的配置
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
toml.dump(config, f)
|
||||
logger.info(f"配置文件已修复: {config_path}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"验证配置文件时出错: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def generate_config():
|
||||
# 获取当前脚本所在的目录
|
||||
@@ -32,13 +109,13 @@ def generate_config():
|
||||
try:
|
||||
with open(config_file_path, "w", encoding="utf-8") as f:
|
||||
f.write(CONFIG_CONTENT)
|
||||
print(f"配置文件已生成: {config_file_path}")
|
||||
print("请记得编辑该文件,填入您的火山引擎API 密钥。")
|
||||
logger.info(f"配置文件已生成: {config_file_path}")
|
||||
logger.info("请记得编辑该文件,填入您的火山引擎API 密钥。")
|
||||
except IOError as e:
|
||||
print(f"错误:无法写入配置文件 {config_file_path}。原因: {e}")
|
||||
# else:
|
||||
# print(f"配置文件已存在: {config_file_path}")
|
||||
# print("未进行任何更改。如果您想重新生成,请先删除或重命名现有文件。")
|
||||
logger.error(f"错误:无法写入配置文件 {config_file_path}。原因: {e}")
|
||||
else:
|
||||
# 验证并修复现有配置
|
||||
validate_and_fix_config(config_file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,6 +6,7 @@ import base64 # 新增:用于Base64编码
|
||||
import traceback # 新增:用于打印堆栈跟踪
|
||||
from typing import Tuple
|
||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action
|
||||
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType
|
||||
from src.common.logger_manager import get_logger
|
||||
from .generate_pic_config import generate_config
|
||||
|
||||
@@ -34,9 +35,50 @@ class PicAction(PluginAction):
|
||||
"当有人要求你生成并发送一张图片时使用",
|
||||
"当有人让你画一张图时使用",
|
||||
]
|
||||
default = False
|
||||
default = True
|
||||
action_config_file_name = "pic_action_config.toml"
|
||||
|
||||
# 激活类型设置 - 使用LLM判定,能更好理解用户意图
|
||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
||||
llm_judge_prompt = """
|
||||
判定是否需要使用图片生成动作的条件:
|
||||
1. 用户明确要求画图、生成图片或创作图像
|
||||
2. 用户描述了想要看到的画面或场景
|
||||
3. 对话中提到需要视觉化展示某些概念
|
||||
4. 用户想要创意图片或艺术作品
|
||||
|
||||
适合使用的情况:
|
||||
- "画一张..."、"画个..."、"生成图片"
|
||||
- "我想看看...的样子"
|
||||
- "能画出...吗"
|
||||
- "创作一幅..."
|
||||
|
||||
绝对不要使用的情况:
|
||||
1. 纯文字聊天和问答
|
||||
2. 只是提到"图片"、"画"等词但不是要求生成
|
||||
3. 谈论已存在的图片或照片
|
||||
4. 技术讨论中提到绘图概念但无生成需求
|
||||
5. 用户明确表示不需要图片时
|
||||
"""
|
||||
|
||||
# 简单的请求缓存,避免短时间内重复请求
|
||||
_request_cache = {}
|
||||
_cache_max_size = 10
|
||||
|
||||
@classmethod
|
||||
def _get_cache_key(cls, description: str, model: str, size: str) -> str:
|
||||
"""生成缓存键"""
|
||||
return f"{description[:100]}|{model}|{size}" # 限制描述长度避免键过长
|
||||
|
||||
@classmethod
|
||||
def _cleanup_cache(cls):
|
||||
"""清理缓存,保持大小在限制内"""
|
||||
if len(cls._request_cache) > cls._cache_max_size:
|
||||
# 简单的FIFO策略,移除最旧的条目
|
||||
keys_to_remove = list(cls._request_cache.keys())[:-cls._cache_max_size//2]
|
||||
for key in keys_to_remove:
|
||||
del cls._request_cache[key]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
action_data: dict,
|
||||
@@ -66,6 +108,7 @@ class PicAction(PluginAction):
|
||||
"""处理图片生成动作(通过HTTP API)"""
|
||||
logger.info(f"{self.log_prefix} 执行 pic_action (HTTP): {self.reasoning}")
|
||||
|
||||
# 配置验证
|
||||
http_base_url = self.config.get("base_url")
|
||||
http_api_key = self.config.get("volcano_generate_api_key")
|
||||
|
||||
@@ -75,15 +118,51 @@ class PicAction(PluginAction):
|
||||
logger.error(f"{self.log_prefix} HTTP调用配置缺失: base_url 或 volcano_generate_api_key.")
|
||||
return False, "HTTP配置不完整"
|
||||
|
||||
# API密钥验证
|
||||
if http_api_key == "YOUR_VOLCANO_GENERATE_API_KEY_HERE":
|
||||
error_msg = "图片生成功能尚未配置,请设置正确的API密钥。"
|
||||
await self.send_message_by_expressor(error_msg)
|
||||
logger.error(f"{self.log_prefix} API密钥未配置")
|
||||
return False, "API密钥未配置"
|
||||
|
||||
# 参数验证
|
||||
description = self.action_data.get("description")
|
||||
if not description:
|
||||
if not description or not description.strip():
|
||||
logger.warning(f"{self.log_prefix} 图片描述为空,无法生成图片。")
|
||||
await self.send_message_by_expressor("你需要告诉我想要画什么样的图片哦~")
|
||||
await self.send_message_by_expressor("你需要告诉我想要画什么样的图片哦~ 比如说'画一只可爱的小猫'")
|
||||
return False, "图片描述为空"
|
||||
|
||||
# 清理和验证描述
|
||||
description = description.strip()
|
||||
if len(description) > 1000: # 限制描述长度
|
||||
description = description[:1000]
|
||||
logger.info(f"{self.log_prefix} 图片描述过长,已截断")
|
||||
|
||||
# 获取配置
|
||||
default_model = self.config.get("default_model", "doubao-seedream-3-0-t2i-250415")
|
||||
image_size = self.action_data.get("size", self.config.get("default_size", "1024x1024"))
|
||||
|
||||
# 验证图片尺寸格式
|
||||
if not self._validate_image_size(image_size):
|
||||
logger.warning(f"{self.log_prefix} 无效的图片尺寸: {image_size},使用默认值")
|
||||
image_size = "1024x1024"
|
||||
|
||||
# 检查缓存
|
||||
cache_key = self._get_cache_key(description, default_model, image_size)
|
||||
if cache_key in self._request_cache:
|
||||
cached_result = self._request_cache[cache_key]
|
||||
logger.info(f"{self.log_prefix} 使用缓存的图片结果")
|
||||
await self.send_message_by_expressor("我之前画过类似的图片,用之前的结果~")
|
||||
|
||||
# 直接发送缓存的结果
|
||||
send_success = await self.send_message(type="image", data=cached_result)
|
||||
if send_success:
|
||||
await self.send_message_by_expressor("图片表情已发送!")
|
||||
return True, "图片表情已发送(缓存)"
|
||||
else:
|
||||
# 缓存失败,清除这个缓存项并继续正常流程
|
||||
del self._request_cache[cache_key]
|
||||
|
||||
# guidance_scale 现在完全由配置文件控制
|
||||
guidance_scale_input = self.config.get("default_guidance_scale", 2.5) # 默认2.5
|
||||
guidance_scale_val = 2.5 # Fallback default
|
||||
@@ -160,6 +239,10 @@ class PicAction(PluginAction):
|
||||
base64_image_string = encode_result
|
||||
send_success = await self.send_message(type="image", data=base64_image_string)
|
||||
if send_success:
|
||||
# 缓存成功的结果
|
||||
self._request_cache[cache_key] = base64_image_string
|
||||
self._cleanup_cache()
|
||||
|
||||
await self.send_message_by_expressor("图片表情已发送!")
|
||||
return True, "图片表情已发送"
|
||||
else:
|
||||
@@ -267,3 +350,11 @@ class PicAction(PluginAction):
|
||||
logger.error(f"{self.log_prefix} (HTTP) 图片生成时意外错误: {e!r}", exc_info=True)
|
||||
traceback.print_exc()
|
||||
return False, f"图片生成HTTP请求时发生意外错误: {str(e)[:100]}"
|
||||
|
||||
def _validate_image_size(self, image_size: str) -> bool:
|
||||
"""验证图片尺寸格式"""
|
||||
try:
|
||||
width, height = map(int, image_size.split('x'))
|
||||
return 100 <= width <= 10000 and 100 <= height <= 10000
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
# 火山方舟 API 的基础 URL
|
||||
base_url = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
# 用于图片生成的API密钥
|
||||
volcano_generate_api_key = "YOUR_VOLCANO_GENERATE_API_KEY_HERE"
|
||||
# 默认图片生成模型
|
||||
default_model = "doubao-seedream-3-0-t2i-250415"
|
||||
# 默认图片尺寸
|
||||
default_size = "1024x1024"
|
||||
|
||||
|
||||
# 是否默认开启水印
|
||||
default_watermark = true
|
||||
# 默认引导强度
|
||||
default_guidance_scale = 2.5
|
||||
# 默认随机种子
|
||||
default_seed = 42
|
||||
|
||||
# 更多插件特定配置可以在此添加...
|
||||
# custom_parameter = "some_value"
|
||||
cache_enabled = true
|
||||
cache_max_size = 10
|
||||
|
||||
19
src/plugins/doubao_pic/actions/pic_action_config.toml.backup
Normal file
19
src/plugins/doubao_pic/actions/pic_action_config.toml.backup
Normal file
@@ -0,0 +1,19 @@
|
||||
# 火山方舟 API 的基础 URL
|
||||
base_url = "https://ark.cn-beijing.volces.com/api/v3"
|
||||
# 用于图片生成的API密钥
|
||||
volcano_generate_api_key = "YOUR_VOLCANO_GENERATE_API_KEY_HERE"
|
||||
# 默认图片生成模型
|
||||
default_model = "doubao-seedream-3-0-t2i-250415"
|
||||
# 默认图片尺寸
|
||||
default_size = "1024x1024"
|
||||
|
||||
|
||||
# 是否默认开启水印
|
||||
default_watermark = true
|
||||
# 默认引导强度
|
||||
default_guidance_scale = 2.5
|
||||
# 默认随机种子
|
||||
default_seed = 42
|
||||
|
||||
# 更多插件特定配置可以在此添加...
|
||||
# custom_parameter = "some_value"
|
||||
@@ -1,4 +1,21 @@
|
||||
"""测试插件包"""
|
||||
"""禁言插件包
|
||||
|
||||
这是一个群聊管理插件,提供智能禁言功能。
|
||||
|
||||
功能特性:
|
||||
- 智能LLM判定:根据聊天内容智能判断是否需要禁言
|
||||
- 灵活的时长管理:支持自定义禁言时长限制
|
||||
- 模板化消息:支持自定义禁言提示消息
|
||||
- 参数验证:完整的输入参数验证和错误处理
|
||||
- 配置文件支持:所有设置可通过配置文件调整
|
||||
|
||||
使用场景:
|
||||
- 用户发送违规内容时自动判定禁言
|
||||
- 用户主动要求被禁言时执行操作
|
||||
- 管理员通过聊天指令触发禁言动作
|
||||
|
||||
配置文件:src/plugins/mute_plugin/actions/mute_action_config.toml
|
||||
"""
|
||||
|
||||
"""
|
||||
这是一个测试插件
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action
|
||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
||||
from typing import Tuple
|
||||
|
||||
logger = get_logger("mute_action")
|
||||
@@ -22,9 +22,102 @@ class MuteAction(PluginAction):
|
||||
"当有人发了擦边,或者色情内容时使用",
|
||||
"当有人要求禁言自己时使用",
|
||||
]
|
||||
default = False # 默认动作,是否手动添加到使用集
|
||||
default = True # 默认动作,是否手动添加到使用集
|
||||
associated_types = ["command", "text"]
|
||||
# associated_types = ["text"]
|
||||
action_config_file_name = "mute_action_config.toml"
|
||||
|
||||
# 激活类型设置 - 使用LLM判定,因为禁言是严肃的管理动作,需要谨慎判断
|
||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
||||
llm_judge_prompt = """
|
||||
判定是否需要使用禁言动作的严格条件:
|
||||
|
||||
必须使用禁言的情况:
|
||||
1. 用户发送明显违规内容(色情、暴力、政治敏感等)
|
||||
2. 恶意刷屏或垃圾信息轰炸
|
||||
3. 用户主动明确要求被禁言("禁言我"等)
|
||||
4. 严重违反群规的行为
|
||||
5. 恶意攻击他人或群组管理
|
||||
|
||||
绝对不要使用的情况:
|
||||
1. 正常聊天和讨论,即使话题敏感
|
||||
2. 情绪化表达但无恶意
|
||||
3. 开玩笑或调侃,除非过分
|
||||
4. 单纯的意见分歧或争论
|
||||
5. 轻微的不当言论(应优先提醒)
|
||||
6. 用户只是提到"禁言"词汇但非要求
|
||||
|
||||
注意:禁言是严厉措施,只在明确违规或用户主动要求时使用。
|
||||
宁可保守也不要误判,保护用户的发言权利。
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 生成配置文件(如果不存在)
|
||||
self._generate_config_if_needed()
|
||||
|
||||
def _generate_config_if_needed(self):
|
||||
"""生成配置文件(如果不存在)"""
|
||||
import os
|
||||
|
||||
# 获取动作文件所在目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
config_path = os.path.join(current_dir, "mute_action_config.toml")
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
config_content = """\
|
||||
# 禁言动作配置文件
|
||||
|
||||
# 默认禁言时长限制(秒)
|
||||
min_duration = 60 # 最短禁言时长
|
||||
max_duration = 2592000 # 最长禁言时长(30天)
|
||||
default_duration = 300 # 默认禁言时长(5分钟)
|
||||
|
||||
# 禁言消息模板
|
||||
templates = [
|
||||
"好的,禁言 {target} {duration},理由:{reason}",
|
||||
"收到,对 {target} 执行禁言 {duration},因为{reason}",
|
||||
"明白了,禁言 {target} {duration},原因是{reason}"
|
||||
]
|
||||
|
||||
# 错误消息模板
|
||||
error_messages = [
|
||||
"没有指定禁言对象呢~",
|
||||
"没有指定禁言时长呢~",
|
||||
"禁言时长必须是正数哦~",
|
||||
"禁言时长必须是数字哦~",
|
||||
"找不到 {target} 这个人呢~",
|
||||
"查找用户信息时出现问题~"
|
||||
]
|
||||
|
||||
# 是否启用时长美化显示
|
||||
enable_duration_formatting = true
|
||||
|
||||
# 是否记录禁言历史
|
||||
log_mute_history = true
|
||||
"""
|
||||
try:
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
f.write(config_content)
|
||||
logger.info(f"已生成禁言动作配置文件: {config_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"生成配置文件失败: {e}")
|
||||
|
||||
def _get_duration_limits(self) -> tuple[int, int, int]:
|
||||
"""获取时长限制配置"""
|
||||
min_dur = self.config.get("min_duration", 60)
|
||||
max_dur = self.config.get("max_duration", 2592000)
|
||||
default_dur = self.config.get("default_duration", 300)
|
||||
return min_dur, max_dur, default_dur
|
||||
|
||||
def _get_template_message(self, target: str, duration_str: str, reason: str) -> str:
|
||||
"""获取模板化的禁言消息"""
|
||||
templates = self.config.get("templates", [
|
||||
"好的,禁言 {target} {duration},理由:{reason}"
|
||||
])
|
||||
|
||||
import random
|
||||
template = random.choice(templates)
|
||||
return template.format(target=target, duration=duration_str, reason=reason)
|
||||
|
||||
async def process(self) -> Tuple[bool, str]:
|
||||
"""处理群聊禁言动作"""
|
||||
@@ -35,47 +128,115 @@ class MuteAction(PluginAction):
|
||||
duration = self.action_data.get("duration")
|
||||
reason = self.action_data.get("reason", "违反群规")
|
||||
|
||||
if not target or not duration:
|
||||
error_msg = "禁言参数不完整,需要target和duration"
|
||||
# 参数验证
|
||||
if not target:
|
||||
error_msg = "禁言目标不能为空"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
await self.send_message_by_expressor("没有指定禁言对象呢~")
|
||||
return False, error_msg
|
||||
|
||||
if not duration:
|
||||
error_msg = "禁言时长不能为空"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
await self.send_message_by_expressor("没有指定禁言时长呢~")
|
||||
return False, error_msg
|
||||
|
||||
# 获取时长限制配置
|
||||
min_duration, max_duration, default_duration = self._get_duration_limits()
|
||||
|
||||
# 验证时长格式并转换
|
||||
try:
|
||||
duration_int = int(duration)
|
||||
if duration_int <= 0:
|
||||
error_msg = "禁言时长必须大于0"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
error_templates = self.config.get("error_messages", ["禁言时长必须是正数哦~"])
|
||||
await self.send_message_by_expressor(error_templates[2] if len(error_templates) > 2 else "禁言时长必须是正数哦~")
|
||||
return False, error_msg
|
||||
|
||||
# 限制禁言时长范围
|
||||
if duration_int < min_duration:
|
||||
duration_int = min_duration
|
||||
logger.info(f"{self.log_prefix} 禁言时长过短,调整为{min_duration}秒")
|
||||
elif duration_int > max_duration:
|
||||
duration_int = max_duration
|
||||
logger.info(f"{self.log_prefix} 禁言时长过长,调整为{max_duration}秒")
|
||||
|
||||
except (ValueError, TypeError) as e:
|
||||
error_msg = f"禁言时长格式无效: {duration}"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
error_templates = self.config.get("error_messages", ["禁言时长必须是数字哦~"])
|
||||
await self.send_message_by_expressor(error_templates[3] if len(error_templates) > 3 else "禁言时长必须是数字哦~")
|
||||
return False, error_msg
|
||||
|
||||
# 获取用户ID
|
||||
try:
|
||||
platform, user_id = await self.get_user_id_by_person_name(target)
|
||||
except Exception as e:
|
||||
error_msg = f"查找用户ID时出错: {e}"
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
await self.send_message_by_expressor("查找用户信息时出现问题~")
|
||||
return False, error_msg
|
||||
|
||||
if not user_id:
|
||||
error_msg = f"未找到用户 {target} 的ID"
|
||||
await self.send_message_by_expressor(f"压根没 {target} 这个人")
|
||||
await self.send_message_by_expressor(f"找不到 {target} 这个人呢~")
|
||||
logger.error(f"{self.log_prefix} {error_msg}")
|
||||
return False, error_msg
|
||||
|
||||
# 发送表达情绪的消息
|
||||
await self.send_message_by_expressor(f"禁言{target} {duration}秒,因为{reason}")
|
||||
enable_formatting = self.config.get("enable_duration_formatting", True)
|
||||
time_str = self._format_duration(duration_int) if enable_formatting else f"{duration_int}秒"
|
||||
|
||||
# 使用模板化消息
|
||||
message = self._get_template_message(target, time_str, reason)
|
||||
await self.send_message_by_expressor(message)
|
||||
|
||||
try:
|
||||
# 确保duration是字符串类型
|
||||
if int(duration) < 60:
|
||||
duration = 60
|
||||
if int(duration) > 3600 * 24 * 30:
|
||||
duration = 3600 * 24 * 30
|
||||
duration_str = str(int(duration))
|
||||
duration_str = str(duration_int)
|
||||
|
||||
# 发送群聊禁言命令,按照新格式
|
||||
await self.send_message(
|
||||
type="command",
|
||||
data={"name": "GROUP_BAN", "args": {"qq_id": str(user_id), "duration": duration_str}},
|
||||
display_message=f"尝试禁言了 {target} {duration_str}秒",
|
||||
display_message=f"尝试禁言了 {target} {time_str}",
|
||||
)
|
||||
|
||||
await self.store_action_info(
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=f"你尝试禁言了 {target} {duration_str}秒",
|
||||
action_prompt_display=f"你尝试禁言了 {target} {time_str},理由:{reason}",
|
||||
)
|
||||
|
||||
logger.info(f"{self.log_prefix} 成功发送禁言命令,用户 {target}({user_id}),时长 {duration} 秒")
|
||||
return True, f"成功禁言 {target},时长 {duration} 秒"
|
||||
logger.info(f"{self.log_prefix} 成功发送禁言命令,用户 {target}({user_id}),时长 {duration_int} 秒")
|
||||
return True, f"成功禁言 {target},时长 {time_str}"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} 执行禁言动作时出错: {e}")
|
||||
await self.send_message_by_expressor(f"执行禁言动作时出错: {e}")
|
||||
return False, f"执行禁言动作时出错: {e}"
|
||||
|
||||
def _format_duration(self, seconds: int) -> str:
|
||||
"""将秒数格式化为可读的时间字符串"""
|
||||
if seconds < 60:
|
||||
return f"{seconds}秒"
|
||||
elif seconds < 3600:
|
||||
minutes = seconds // 60
|
||||
remaining_seconds = seconds % 60
|
||||
if remaining_seconds > 0:
|
||||
return f"{minutes}分{remaining_seconds}秒"
|
||||
else:
|
||||
return f"{minutes}分钟"
|
||||
elif seconds < 86400:
|
||||
hours = seconds // 3600
|
||||
remaining_minutes = (seconds % 3600) // 60
|
||||
if remaining_minutes > 0:
|
||||
return f"{hours}小时{remaining_minutes}分钟"
|
||||
else:
|
||||
return f"{hours}小时"
|
||||
else:
|
||||
days = seconds // 86400
|
||||
remaining_hours = (seconds % 86400) // 3600
|
||||
if remaining_hours > 0:
|
||||
return f"{days}天{remaining_hours}小时"
|
||||
else:
|
||||
return f"{days}天"
|
||||
|
||||
29
src/plugins/mute_plugin/actions/mute_action_config.toml
Normal file
29
src/plugins/mute_plugin/actions/mute_action_config.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
# 禁言动作配置文件
|
||||
|
||||
# 默认禁言时长限制(秒)
|
||||
min_duration = 60 # 最短禁言时长
|
||||
max_duration = 2592000 # 最长禁言时长(30天)
|
||||
default_duration = 300 # 默认禁言时长(5分钟)
|
||||
|
||||
# 禁言消息模板
|
||||
templates = [
|
||||
"好的,禁言 {target} {duration},理由:{reason}",
|
||||
"收到,对 {target} 执行禁言 {duration},因为{reason}",
|
||||
"明白了,禁言 {target} {duration},原因是{reason}"
|
||||
]
|
||||
|
||||
# 错误消息模板
|
||||
error_messages = [
|
||||
"没有指定禁言对象呢~",
|
||||
"没有指定禁言时长呢~",
|
||||
"禁言时长必须是正数哦~",
|
||||
"禁言时长必须是数字哦~",
|
||||
"找不到 {target} 这个人呢~",
|
||||
"查找用户信息时出现问题~"
|
||||
]
|
||||
|
||||
# 是否启用时长美化显示
|
||||
enable_duration_formatting = true
|
||||
|
||||
# 是否记录禁言历史
|
||||
log_mute_history = true
|
||||
@@ -1,5 +1,5 @@
|
||||
from src.common.logger_manager import get_logger
|
||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action
|
||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
||||
from typing import Tuple
|
||||
|
||||
logger = get_logger("vtb_action")
|
||||
@@ -23,6 +23,22 @@ class VTBAction(PluginAction):
|
||||
default = True # 设为默认动作
|
||||
associated_types = ["vtb_text"]
|
||||
|
||||
# 激活类型设置 - 使用LLM判定,因为需要根据情感表达需求判断
|
||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
||||
llm_judge_prompt = """
|
||||
判定是否需要使用VTB虚拟主播动作的条件:
|
||||
1. 当前聊天内容涉及明显的情感表达需求
|
||||
2. 用户询问或讨论情感相关话题
|
||||
3. 场景需要生动的情感回应
|
||||
4. 当前回复内容可以通过VTB动作增强表达效果
|
||||
|
||||
不需要使用的情况:
|
||||
1. 纯粹的信息查询
|
||||
2. 技术性问题讨论
|
||||
3. 不涉及情感的日常对话
|
||||
4. 已经有足够的情感表达
|
||||
"""
|
||||
|
||||
async def process(self) -> Tuple[bool, str]:
|
||||
"""处理VTB虚拟主播动作"""
|
||||
logger.info(f"{self.log_prefix} 执行VTB动作: {self.reasoning}")
|
||||
|
||||
Reference in New Issue
Block a user