feat:动作现在区分focus和normal,并且可选不同的激活策略
This commit is contained in:
@@ -39,7 +39,7 @@ async def modify_actions_task():
|
|||||||
|
|
||||||
**处理内容:**
|
**处理内容:**
|
||||||
- 传统观察处理(循环历史分析、类型匹配等)
|
- 传统观察处理(循环历史分析、类型匹配等)
|
||||||
- 激活类型判定(ALWAYS, RANDOM, LLM_JUDGE, KEYWORD)
|
- 双激活类型判定(Focus模式和Normal模式分别处理)
|
||||||
- 并行LLM判定
|
- 并行LLM判定
|
||||||
- 智能缓存
|
- 智能缓存
|
||||||
- 动态关键词收集
|
- 动态关键词收集
|
||||||
@@ -94,41 +94,123 @@ for action_name, action_info in llm_judge_actions.items():
|
|||||||
# 检查消息中的关键词匹配
|
# 检查消息中的关键词匹配
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 双激活类型系统 🆕
|
||||||
|
|
||||||
|
### 系统设计理念
|
||||||
|
**Focus模式** 和 **Normal模式** 采用不同的激活策略:
|
||||||
|
- **Focus模式**: 智能化优先,支持复杂的LLM判定
|
||||||
|
- **Normal模式**: 性能优先,使用快速的关键词和随机触发
|
||||||
|
|
||||||
|
### 双激活类型配置
|
||||||
|
```python
|
||||||
|
class MyAction(BaseAction):
|
||||||
|
action_name = "my_action"
|
||||||
|
action_description = "我的动作"
|
||||||
|
|
||||||
|
# Focus模式激活类型(支持LLM_JUDGE)
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
|
||||||
|
# Normal模式激活类型(建议使用KEYWORD/RANDOM/ALWAYS)
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["关键词1", "keyword"]
|
||||||
|
|
||||||
|
# 模式启用控制
|
||||||
|
mode_enable = ChatMode.ALL # 在所有模式下启用
|
||||||
|
|
||||||
|
# 并行执行控制
|
||||||
|
parallel_action = False # 是否与回复并行执行
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式启用类型 (ChatMode)
|
||||||
|
```python
|
||||||
|
from src.chat.chat_mode import ChatMode
|
||||||
|
|
||||||
|
# 可选值:
|
||||||
|
mode_enable = ChatMode.FOCUS # 仅在Focus模式启用
|
||||||
|
mode_enable = ChatMode.NORMAL # 仅在Normal模式启用
|
||||||
|
mode_enable = ChatMode.ALL # 在所有模式启用(默认)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 并行动作系统 🆕
|
||||||
|
```python
|
||||||
|
# 并行动作:可以与回复生成同时进行
|
||||||
|
parallel_action = True # 不会阻止回复生成
|
||||||
|
|
||||||
|
# 串行动作:会替代回复生成
|
||||||
|
parallel_action = False # 默认值,传统行为
|
||||||
|
```
|
||||||
|
|
||||||
|
**并行动作的优势:**
|
||||||
|
- 提升用户体验(同时获得回复和动作执行)
|
||||||
|
- 减少响应延迟
|
||||||
|
- 适用于情感表达、状态变更等辅助性动作
|
||||||
|
|
||||||
## 四种激活类型
|
## 四种激活类型
|
||||||
|
|
||||||
### 1. ALWAYS - 始终激活
|
### 1. ALWAYS - 始终激活
|
||||||
```python
|
```python
|
||||||
activation_type = ActionActivationType.ALWAYS
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS
|
||||||
# 基础动作,如 reply, no_reply
|
# 基础动作,如 reply, no_reply
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. RANDOM - 随机激活
|
### 2. RANDOM - 随机激活
|
||||||
```python
|
```python
|
||||||
activation_type = ActionActivationType.RANDOM
|
focus_activation_type = ActionActivationType.RANDOM
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
random_probability = 0.3 # 激活概率
|
random_probability = 0.3 # 激活概率
|
||||||
# 用于增加惊喜元素,如随机表情
|
# 用于增加惊喜元素,如随机表情
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. LLM_JUDGE - 智能判定
|
### 3. LLM_JUDGE - 智能判定
|
||||||
```python
|
```python
|
||||||
activation_type = ActionActivationType.LLM_JUDGE
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
llm_judge_prompt = "自定义判定提示词"
|
# 注意:Normal模式不建议使用LLM_JUDGE,会发出警告
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
# 需要理解上下文的复杂动作,如情感表达
|
# 需要理解上下文的复杂动作,如情感表达
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. KEYWORD - 关键词触发
|
### 4. KEYWORD - 关键词触发
|
||||||
```python
|
```python
|
||||||
activation_type = ActionActivationType.KEYWORD
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
activation_keywords = ["画", "图片", "生成"]
|
activation_keywords = ["画", "图片", "生成"]
|
||||||
# 明确指令触发的动作,如图片生成
|
# 明确指令触发的动作,如图片生成
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 推荐配置模式
|
||||||
|
|
||||||
|
### 模式1:智能自适应
|
||||||
|
```python
|
||||||
|
# Focus模式使用智能判定,Normal模式使用关键词
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["相关", "关键词"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式2:统一关键词
|
||||||
|
```python
|
||||||
|
# 两个模式都使用关键词,确保一致性
|
||||||
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["画", "图片", "生成"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式3:Focus专享
|
||||||
|
```python
|
||||||
|
# 仅在Focus模式启用的智能功能
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS # 不会生效
|
||||||
|
mode_enable = ChatMode.FOCUS
|
||||||
|
```
|
||||||
|
|
||||||
## 性能提升
|
## 性能提升
|
||||||
|
|
||||||
### 理论性能改进
|
### 理论性能改进
|
||||||
- **并行LLM判定**: 1.5-2x 提升
|
- **并行LLM判定**: 1.5-2x 提升
|
||||||
- **智能缓存**: 20-30% 额外提升
|
- **智能缓存**: 20-30% 额外提升
|
||||||
- **整体预期**: 2-3x 性能提升
|
- **双模式优化**: Normal模式额外1.5x提升
|
||||||
|
- **整体预期**: 3-5x 性能提升
|
||||||
|
|
||||||
### 缓存策略
|
### 缓存策略
|
||||||
- **缓存键**: `{action_name}_{context_hash}`
|
- **缓存键**: `{action_name}_{context_hash}`
|
||||||
@@ -137,19 +219,43 @@ activation_keywords = ["画", "图片", "生成"]
|
|||||||
|
|
||||||
## 向后兼容性
|
## 向后兼容性
|
||||||
|
|
||||||
### 废弃方法处理
|
### ⚠️ 重大变更说明
|
||||||
|
**旧的 `action_activation_type` 属性已被移除**,必须更新为新的双激活类型系统:
|
||||||
|
|
||||||
|
#### 迁移指南
|
||||||
```python
|
```python
|
||||||
async def process_actions_for_planner(...):
|
# 旧的配置(已废弃)
|
||||||
"""[已废弃] 此方法现在已被整合到 modify_actions() 中"""
|
class OldAction(BaseAction):
|
||||||
logger.warning("process_actions_for_planner() 已废弃")
|
action_activation_type = ActionActivationType.LLM_JUDGE # ❌ 已移除
|
||||||
# 仍然返回结果以保持兼容性
|
|
||||||
return current_using_actions
|
# 新的配置(必须使用)
|
||||||
|
class NewAction(BaseAction):
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE # ✅ Focus模式
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD # ✅ Normal模式
|
||||||
|
activation_keywords = ["相关", "关键词"]
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False
|
||||||
```
|
```
|
||||||
|
|
||||||
### 迁移指南
|
#### 快速迁移脚本
|
||||||
1. **主循环**: 使用 `modify_actions(observations, messages, context, extra)`
|
对于简单的迁移,可以使用以下模式:
|
||||||
2. **规划器**: 直接使用 `ActionManager.get_using_actions()`
|
```python
|
||||||
3. **移除**: 规划器中对 `process_actions_for_planner()` 的调用
|
# 如果原来是 ALWAYS
|
||||||
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS
|
||||||
|
|
||||||
|
# 如果原来是 LLM_JUDGE
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD # 需要添加关键词
|
||||||
|
|
||||||
|
# 如果原来是 KEYWORD
|
||||||
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
|
||||||
|
# 如果原来是 RANDOM
|
||||||
|
focus_activation_type = ActionActivationType.RANDOM
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
|
```
|
||||||
|
|
||||||
## 测试验证
|
## 测试验证
|
||||||
|
|
||||||
@@ -159,11 +265,12 @@ python test_corrected_architecture.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 测试内容
|
### 测试内容
|
||||||
- 架构正确性验证
|
- 双激活类型系统验证
|
||||||
- 数据一致性检查
|
- 数据一致性检查
|
||||||
- 职责分离确认
|
- 职责分离确认
|
||||||
- 性能测试
|
- 性能测试
|
||||||
- 向后兼容性验证
|
- 向后兼容性验证
|
||||||
|
- 并行动作功能验证
|
||||||
|
|
||||||
## 优势总结
|
## 优势总结
|
||||||
|
|
||||||
@@ -175,15 +282,18 @@ python test_corrected_architecture.py
|
|||||||
### 2. 高性能
|
### 2. 高性能
|
||||||
- **并行处理**: 多个LLM判定同时进行
|
- **并行处理**: 多个LLM判定同时进行
|
||||||
- **智能缓存**: 避免重复计算
|
- **智能缓存**: 避免重复计算
|
||||||
|
- **双模式优化**: Focus智能化,Normal快速化
|
||||||
|
|
||||||
### 3. 智能化
|
### 3. 智能化
|
||||||
- **动态配置**: 从动作配置中收集关键词
|
- **动态配置**: 从动作配置中收集关键词
|
||||||
- **上下文感知**: 基于聊天内容智能激活
|
- **上下文感知**: 基于聊天内容智能激活
|
||||||
- **冲突避免**: 防止重复激活
|
- **冲突避免**: 防止重复激活
|
||||||
|
- **模式自适应**: 根据聊天模式选择最优策略
|
||||||
|
|
||||||
### 4. 可扩展性
|
### 4. 可扩展性
|
||||||
- **插件式**: 新的激活类型易于添加
|
- **插件式**: 新的激活类型易于添加
|
||||||
- **配置驱动**: 通过配置控制行为
|
- **配置驱动**: 通过配置控制行为
|
||||||
- **模块化**: 各组件独立可测试
|
- **模块化**: 各组件独立可测试
|
||||||
|
- **双模式支持**: 灵活适应不同使用场景
|
||||||
|
|
||||||
这个修正后的架构实现了正确的职责分工,确保了主循环负责动作管理,规划器专注于决策,同时集成了并行判定和智能缓存等优化功能。
|
这个修正后的架构实现了正确的职责分工,确保了主循环负责动作管理,规划器专注于决策,同时集成了双激活类型、并行判定和智能缓存等优化功能。
|
||||||
@@ -2,44 +2,80 @@
|
|||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
MaiBot 的动作激活系统支持四种不同的激活类型,让机器人能够智能地根据上下文选择合适的动作。
|
MaiBot 的动作激活系统采用**双激活类型架构**,为Focus模式和Normal模式分别提供最优的激活策略。
|
||||||
|
|
||||||
**系统已集成三大优化策略:**
|
**系统已集成四大核心特性:**
|
||||||
|
- 🎯 **双激活类型**:Focus模式智能化,Normal模式高性能
|
||||||
- 🚀 **并行判定**:多个LLM判定任务并行执行
|
- 🚀 **并行判定**:多个LLM判定任务并行执行
|
||||||
- 💾 **智能缓存**:相同上下文的判定结果缓存复用
|
- 💾 **智能缓存**:相同上下文的判定结果缓存复用
|
||||||
- 🔍 **分层判定**:快速过滤 + 精确判定的两层架构
|
- ⚡ **并行动作**:支持与回复同时执行的动作
|
||||||
|
|
||||||
|
## 双激活类型系统 🆕
|
||||||
|
|
||||||
|
### 系统设计理念
|
||||||
|
|
||||||
|
**Focus模式**:智能优先
|
||||||
|
- 支持复杂的LLM判定
|
||||||
|
- 提供精确的上下文理解
|
||||||
|
- 适合需要深度分析的场景
|
||||||
|
|
||||||
|
**Normal模式**:性能优先
|
||||||
|
- 使用快速的关键词匹配
|
||||||
|
- 采用简单的随机触发
|
||||||
|
- 确保快速响应用户
|
||||||
|
|
||||||
|
### 核心属性配置
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
|
||||||
|
from src.chat.chat_mode import ChatMode
|
||||||
|
|
||||||
|
@register_action
|
||||||
|
class MyAction(BaseAction):
|
||||||
|
action_name = "my_action"
|
||||||
|
action_description = "我的动作描述"
|
||||||
|
|
||||||
|
# 双激活类型配置
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE # Focus模式使用智能判定
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD # Normal模式使用关键词
|
||||||
|
activation_keywords = ["关键词1", "关键词2", "keyword"]
|
||||||
|
keyword_case_sensitive = False
|
||||||
|
|
||||||
|
# 模式启用控制
|
||||||
|
mode_enable = ChatMode.ALL # 支持的聊天模式
|
||||||
|
|
||||||
|
# 并行执行控制
|
||||||
|
parallel_action = False # 是否与回复并行执行
|
||||||
|
|
||||||
|
# 插件系统控制
|
||||||
|
enable_plugin = True # 是否启用此插件
|
||||||
|
```
|
||||||
|
|
||||||
## 激活类型详解
|
## 激活类型详解
|
||||||
|
|
||||||
### 1. ALWAYS - 总是激活
|
### 1. ALWAYS - 总是激活
|
||||||
**用途**:基础必需动作,始终可用
|
**用途**:基础必需动作,始终可用
|
||||||
```python
|
```python
|
||||||
action_activation_type = ActionActivationType.ALWAYS
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS
|
||||||
```
|
```
|
||||||
**示例**:`reply_action`, `no_reply_action`
|
**示例**:`reply_action`, `no_reply_action`
|
||||||
|
|
||||||
### 2. RANDOM - 随机激活
|
### 2. RANDOM - 随机激活
|
||||||
**用途**:增加不可预测性和趣味性
|
**用途**:增加不可预测性和趣味性
|
||||||
```python
|
```python
|
||||||
action_activation_type = ActionActivationType.RANDOM
|
focus_activation_type = ActionActivationType.RANDOM
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
random_activation_probability = 0.2 # 20%概率激活
|
random_activation_probability = 0.2 # 20%概率激活
|
||||||
```
|
```
|
||||||
**示例**:`pic_action` (20%概率)
|
**示例**:`vtb_action` (表情动作)
|
||||||
|
|
||||||
### 3. LLM_JUDGE - LLM智能判定
|
### 3. LLM_JUDGE - LLM智能判定
|
||||||
**用途**:需要上下文理解的复杂判定
|
**用途**:需要上下文理解的复杂判定
|
||||||
```python
|
```python
|
||||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
llm_judge_prompt = """
|
# 注意:Normal模式使用LLM_JUDGE会产生性能警告
|
||||||
判定条件:
|
normal_activation_type = ActionActivationType.KEYWORD # 推荐在Normal模式使用KEYWORD
|
||||||
1. 当前聊天涉及情感表达
|
|
||||||
2. 需要生动的情感回应
|
|
||||||
3. 场景适合虚拟主播动作
|
|
||||||
|
|
||||||
不适用场景:
|
|
||||||
1. 纯信息查询
|
|
||||||
2. 技术讨论
|
|
||||||
"""
|
|
||||||
```
|
```
|
||||||
**优化特性**:
|
**优化特性**:
|
||||||
- ⚡ **直接判定**:直接进行LLM判定,减少复杂度
|
- ⚡ **直接判定**:直接进行LLM判定,减少复杂度
|
||||||
@@ -49,11 +85,115 @@ llm_judge_prompt = """
|
|||||||
### 4. KEYWORD - 关键词触发
|
### 4. KEYWORD - 关键词触发
|
||||||
**用途**:精确命令式触发
|
**用途**:精确命令式触发
|
||||||
```python
|
```python
|
||||||
action_activation_type = ActionActivationType.KEYWORD
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
activation_keywords = ["画", "画图", "生成图片", "draw"]
|
activation_keywords = ["画", "画图", "生成图片", "draw"]
|
||||||
keyword_case_sensitive = False # 不区分大小写
|
keyword_case_sensitive = False # 不区分大小写
|
||||||
```
|
```
|
||||||
**示例**:`help_action`, `edge_search_action`, `pic_action`
|
**示例**:`pic_action`, `mute_action`
|
||||||
|
|
||||||
|
## 模式启用控制 (ChatMode)
|
||||||
|
|
||||||
|
### 模式类型
|
||||||
|
```python
|
||||||
|
from src.chat.chat_mode import ChatMode
|
||||||
|
|
||||||
|
# 在所有模式下启用
|
||||||
|
mode_enable = ChatMode.ALL # 默认值
|
||||||
|
|
||||||
|
# 仅在Focus模式启用
|
||||||
|
mode_enable = ChatMode.FOCUS
|
||||||
|
|
||||||
|
# 仅在Normal模式启用
|
||||||
|
mode_enable = ChatMode.NORMAL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用场景建议
|
||||||
|
- **ChatMode.ALL**: 通用功能(如回复、图片生成)
|
||||||
|
- **ChatMode.FOCUS**: 需要深度理解的智能功能
|
||||||
|
- **ChatMode.NORMAL**: 快速响应的基础功能
|
||||||
|
|
||||||
|
## 并行动作系统 🆕
|
||||||
|
|
||||||
|
### 概念说明
|
||||||
|
```python
|
||||||
|
# 并行动作:与回复生成同时执行
|
||||||
|
parallel_action = True # 不会阻止回复,提升用户体验
|
||||||
|
|
||||||
|
# 串行动作:替代回复生成(传统行为)
|
||||||
|
parallel_action = False # 默认值,动作执行时不生成回复
|
||||||
|
```
|
||||||
|
|
||||||
|
### 适用场景
|
||||||
|
**并行动作 (parallel_action = True)**:
|
||||||
|
- 情感表达(表情、动作)
|
||||||
|
- 状态变更(禁言、设置)
|
||||||
|
- 辅助功能(TTS播报)
|
||||||
|
|
||||||
|
**串行动作 (parallel_action = False)**:
|
||||||
|
- 内容生成(图片、文档)
|
||||||
|
- 搜索查询
|
||||||
|
- 需要完整注意力的操作
|
||||||
|
|
||||||
|
### 实际案例
|
||||||
|
```python
|
||||||
|
@register_action
|
||||||
|
class MuteAction(PluginAction):
|
||||||
|
action_name = "mute_action"
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["禁言", "mute", "ban", "silence"]
|
||||||
|
parallel_action = True # 禁言的同时还可以回复确认信息
|
||||||
|
|
||||||
|
@register_action
|
||||||
|
class PicAction(PluginAction):
|
||||||
|
action_name = "pic_action"
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["画", "绘制", "生成图片", "画图", "draw", "paint"]
|
||||||
|
parallel_action = False # 专注于图片生成,不同时回复
|
||||||
|
```
|
||||||
|
|
||||||
|
## 推荐配置模式
|
||||||
|
|
||||||
|
### 模式1:智能自适应(推荐)
|
||||||
|
```python
|
||||||
|
# Focus模式智能判定,Normal模式快速触发
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["相关", "关键词", "英文keyword"]
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False # 根据具体需求调整
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式2:统一关键词
|
||||||
|
```python
|
||||||
|
# 两个模式都使用关键词,确保行为一致
|
||||||
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["画", "图片", "生成"]
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式3:Focus专享功能
|
||||||
|
```python
|
||||||
|
# 仅在Focus模式启用的高级功能
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS # 不会生效
|
||||||
|
mode_enable = ChatMode.FOCUS
|
||||||
|
parallel_action = False
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模式4:随机娱乐功能
|
||||||
|
```python
|
||||||
|
# 增加趣味性的随机功能
|
||||||
|
focus_activation_type = ActionActivationType.RANDOM
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
|
random_activation_probability = 0.08 # 8%概率
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = True # 通常与回复并行
|
||||||
|
```
|
||||||
|
|
||||||
## 性能优化详解
|
## 性能优化详解
|
||||||
|
|
||||||
@@ -194,26 +334,22 @@ focus_chat:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
||||||
|
from src.chat.chat_mode import ChatMode
|
||||||
|
|
||||||
@register_action
|
@register_action
|
||||||
class MyAction(PluginAction):
|
class MyAction(PluginAction):
|
||||||
action_name = "my_action"
|
action_name = "my_action"
|
||||||
action_description = "我的自定义动作"
|
action_description = "我的自定义动作"
|
||||||
|
|
||||||
# 选择合适的激活类型
|
# 双激活类型配置
|
||||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["自定义", "触发", "custom"]
|
||||||
|
|
||||||
# LLM判定的自定义提示词
|
# 模式和并行控制
|
||||||
llm_judge_prompt = """
|
mode_enable = ChatMode.ALL
|
||||||
判定是否激活my_action的条件:
|
parallel_action = False
|
||||||
1. 用户明确要求执行特定操作
|
enable_plugin = True
|
||||||
2. 当前场景适合此动作
|
|
||||||
3. 没有其他更合适的动作
|
|
||||||
|
|
||||||
不应激活的情况:
|
|
||||||
1. 普通聊天对话
|
|
||||||
2. 用户只是随便说说
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def process(self):
|
async def process(self):
|
||||||
# 动作执行逻辑
|
# 动作执行逻辑
|
||||||
@@ -225,9 +361,12 @@ class MyAction(PluginAction):
|
|||||||
@register_action
|
@register_action
|
||||||
class SearchAction(PluginAction):
|
class SearchAction(PluginAction):
|
||||||
action_name = "search_action"
|
action_name = "search_action"
|
||||||
action_activation_type = ActionActivationType.KEYWORD
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
activation_keywords = ["搜索", "查找", "什么是", "search", "find"]
|
activation_keywords = ["搜索", "查找", "什么是", "search", "find"]
|
||||||
keyword_case_sensitive = False
|
keyword_case_sensitive = False
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False
|
||||||
```
|
```
|
||||||
|
|
||||||
### 随机触发动作
|
### 随机触发动作
|
||||||
@@ -235,8 +374,51 @@ class SearchAction(PluginAction):
|
|||||||
@register_action
|
@register_action
|
||||||
class SurpriseAction(PluginAction):
|
class SurpriseAction(PluginAction):
|
||||||
action_name = "surprise_action"
|
action_name = "surprise_action"
|
||||||
action_activation_type = ActionActivationType.RANDOM
|
focus_activation_type = ActionActivationType.RANDOM
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
random_activation_probability = 0.1 # 10%概率
|
random_activation_probability = 0.1 # 10%概率
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = True # 惊喜动作与回复并行
|
||||||
|
```
|
||||||
|
|
||||||
|
### Focus专享智能动作
|
||||||
|
```python
|
||||||
|
@register_action
|
||||||
|
class AdvancedAnalysisAction(PluginAction):
|
||||||
|
action_name = "advanced_analysis"
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS # 不会生效
|
||||||
|
mode_enable = ChatMode.FOCUS # 仅Focus模式
|
||||||
|
parallel_action = False
|
||||||
|
```
|
||||||
|
|
||||||
|
## 现有插件的配置示例
|
||||||
|
|
||||||
|
### MuteAction (禁言动作)
|
||||||
|
```python
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["禁言", "mute", "ban", "silence"]
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = True # 可以与回复同时进行
|
||||||
|
```
|
||||||
|
|
||||||
|
### PicAction (图片生成)
|
||||||
|
```python
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["画", "绘制", "生成图片", "画图", "draw", "paint", "图片生成"]
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False # 专注生成,不同时回复
|
||||||
|
```
|
||||||
|
|
||||||
|
### VTBAction (虚拟主播表情)
|
||||||
|
```python
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
|
random_activation_probability = 0.08
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False # 替代文字回复
|
||||||
```
|
```
|
||||||
|
|
||||||
## 性能监控
|
## 性能监控
|
||||||
@@ -257,6 +439,101 @@ logger.debug(f"并行调整动作、回忆和处理完成,耗时: {duration:.2
|
|||||||
3. **监控并行效果**:关注 `asyncio.gather` 的执行时间
|
3. **监控并行效果**:关注 `asyncio.gather` 的执行时间
|
||||||
4. **缓存命中率**:监控缓存使用情况,优化策略
|
4. **缓存命中率**:监控缓存使用情况,优化策略
|
||||||
5. **启用流程并行化**:确保 `parallel_processing` 配置为 `true`
|
5. **启用流程并行化**:确保 `parallel_processing` 配置为 `true`
|
||||||
|
6. **激活类型选择**:Normal模式优先使用KEYWORD,避免LLM_JUDGE
|
||||||
|
|
||||||
|
## 迁移指南 ⚠️
|
||||||
|
|
||||||
|
### 重大变更说明
|
||||||
|
**旧的 `action_activation_type` 属性已被移除**,必须更新为新的双激活类型系统。
|
||||||
|
|
||||||
|
### 快速迁移步骤
|
||||||
|
|
||||||
|
#### 第一步:更新基本属性
|
||||||
|
```python
|
||||||
|
# 旧的配置(已废弃)❌
|
||||||
|
class OldAction(BaseAction):
|
||||||
|
action_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
|
||||||
|
# 新的配置(必须使用)✅
|
||||||
|
class NewAction(BaseAction):
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
activation_keywords = ["相关", "关键词"]
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False
|
||||||
|
enable_plugin = True
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第二步:根据原类型选择对应策略
|
||||||
|
```python
|
||||||
|
# 原来是 ALWAYS
|
||||||
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS
|
||||||
|
|
||||||
|
# 原来是 LLM_JUDGE
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD # 添加关键词
|
||||||
|
activation_keywords = ["需要", "添加", "关键词"]
|
||||||
|
|
||||||
|
# 原来是 KEYWORD
|
||||||
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
# 保持原有的 activation_keywords
|
||||||
|
|
||||||
|
# 原来是 RANDOM
|
||||||
|
focus_activation_type = ActionActivationType.RANDOM
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
|
# 保持原有的 random_activation_probability
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第三步:配置新功能
|
||||||
|
```python
|
||||||
|
# 添加模式控制
|
||||||
|
mode_enable = ChatMode.ALL # 或 ChatMode.FOCUS / ChatMode.NORMAL
|
||||||
|
|
||||||
|
# 添加并行控制
|
||||||
|
parallel_action = False # 根据动作特性选择True/False
|
||||||
|
|
||||||
|
# 添加插件控制
|
||||||
|
enable_plugin = True # 是否启用此插件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量迁移脚本
|
||||||
|
可以创建以下脚本来帮助批量迁移:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# migrate_actions.py
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
def migrate_action_file(filepath):
|
||||||
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# 替换 action_activation_type
|
||||||
|
if 'action_activation_type = ActionActivationType.ALWAYS' in content:
|
||||||
|
content = content.replace(
|
||||||
|
'action_activation_type = ActionActivationType.ALWAYS',
|
||||||
|
'focus_activation_type = ActionActivationType.ALWAYS\n normal_activation_type = ActionActivationType.ALWAYS'
|
||||||
|
)
|
||||||
|
elif 'action_activation_type = ActionActivationType.LLM_JUDGE' in content:
|
||||||
|
content = content.replace(
|
||||||
|
'action_activation_type = ActionActivationType.LLM_JUDGE',
|
||||||
|
'focus_activation_type = ActionActivationType.LLM_JUDGE\n normal_activation_type = ActionActivationType.KEYWORD\n activation_keywords = ["需要", "添加", "关键词"] # TODO: 配置合适的关键词'
|
||||||
|
)
|
||||||
|
# ... 其他替换逻辑
|
||||||
|
|
||||||
|
# 添加新属性
|
||||||
|
if 'mode_enable' not in content:
|
||||||
|
# 在class定义后添加新属性
|
||||||
|
# ...
|
||||||
|
|
||||||
|
with open(filepath, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
migrate_action_file('src/plugins/your_plugin/actions/your_action.py')
|
||||||
|
```
|
||||||
|
|
||||||
## 测试验证
|
## 测试验证
|
||||||
|
|
||||||
@@ -271,41 +548,54 @@ python test_parallel_optimization.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
测试内容包括:
|
测试内容包括:
|
||||||
|
- ✅ 双激活类型功能验证
|
||||||
- ✅ 并行处理功能验证
|
- ✅ 并行处理功能验证
|
||||||
- ✅ 缓存机制效果测试
|
- ✅ 缓存机制效果测试
|
||||||
- ✅ 分层判定规则验证
|
- ✅ 分层判定规则验证
|
||||||
- ✅ 性能对比分析
|
- ✅ 性能对比分析
|
||||||
- ✅ HFC流程并行化效果
|
- ✅ HFC流程并行化效果
|
||||||
- ✅ 多循环平均性能测试
|
- ✅ 多循环平均性能测试
|
||||||
|
- ✅ 并行动作系统验证
|
||||||
|
- ✅ 迁移兼容性测试
|
||||||
|
|
||||||
## 最佳实践
|
## 最佳实践
|
||||||
|
|
||||||
### 1. 激活类型选择
|
### 1. 激活类型选择
|
||||||
- **ALWAYS**:reply, no_reply 等基础动作
|
- **ALWAYS**:reply, no_reply 等基础动作
|
||||||
- **LLM_JUDGE**:需要智能判断的复杂动作
|
- **LLM_JUDGE**:需要智能判断的复杂动作(建议仅用于Focus模式)
|
||||||
- **KEYWORD**:明确的命令式动作
|
- **KEYWORD**:明确的命令式动作(推荐在Normal模式使用)
|
||||||
- **RANDOM**:增趣动作,低概率触发
|
- **RANDOM**:增趣动作,低概率触发
|
||||||
|
|
||||||
### 2. LLM判定提示词编写
|
### 2. 双模式配置策略
|
||||||
|
- **智能自适应**:Focus用LLM_JUDGE,Normal用KEYWORD
|
||||||
|
- **性能优先**:两个模式都用KEYWORD或RANDOM
|
||||||
|
- **功能分离**:某些功能仅在特定模式启用
|
||||||
|
|
||||||
|
### 3. 并行动作使用建议
|
||||||
|
- **parallel_action = True**:辅助性、非内容生成类动作
|
||||||
|
- **parallel_action = False**:主要内容生成、需要完整注意力的动作
|
||||||
|
|
||||||
|
### 4. LLM判定提示词编写
|
||||||
- 明确描述激活条件和排除条件
|
- 明确描述激活条件和排除条件
|
||||||
- 避免模糊的描述
|
- 避免模糊的描述
|
||||||
- 考虑边界情况
|
- 考虑边界情况
|
||||||
- 保持简洁明了
|
- 保持简洁明了
|
||||||
|
|
||||||
### 3. 关键词设置
|
### 5. 关键词设置
|
||||||
- 包含同义词和英文对应词
|
- 包含同义词和英文对应词
|
||||||
- 考虑用户的不同表达习惯
|
- 考虑用户的不同表达习惯
|
||||||
- 避免过于宽泛的关键词
|
- 避免过于宽泛的关键词
|
||||||
- 根据实际使用调整
|
- 根据实际使用调整
|
||||||
|
|
||||||
### 4. 性能优化
|
### 6. 性能优化
|
||||||
- 定期监控处理时间
|
- 定期监控处理时间
|
||||||
- 根据使用模式调整缓存策略
|
- 根据使用模式调整缓存策略
|
||||||
- 优化激活判定逻辑
|
- 优化激活判定逻辑
|
||||||
- 平衡准确性和性能
|
- 平衡准确性和性能
|
||||||
- **启用并行处理配置**
|
- **启用并行处理配置**
|
||||||
|
- **Normal模式避免使用LLM_JUDGE**
|
||||||
|
|
||||||
### 5. 并行化最佳实践
|
### 7. 并行化最佳实践
|
||||||
- 在生产环境启用 `parallel_processing`
|
- 在生产环境启用 `parallel_processing`
|
||||||
- 监控并行阶段的执行时间
|
- 监控并行阶段的执行时间
|
||||||
- 确保各阶段的独立性
|
- 确保各阶段的独立性
|
||||||
@@ -313,30 +603,48 @@ python test_parallel_optimization.py
|
|||||||
|
|
||||||
## 总结
|
## 总结
|
||||||
|
|
||||||
优化后的动作激活系统通过**四层优化策略**,实现了全方位的性能提升:
|
优化后的动作激活系统通过**五层优化策略**,实现了全方位的性能提升:
|
||||||
|
|
||||||
### 第一层:动作激活内部优化
|
### 第一层:双激活类型系统
|
||||||
|
- **Focus模式**:智能化优先,支持复杂LLM判定
|
||||||
|
- **Normal模式**:性能优先,使用快速关键词匹配
|
||||||
|
- **模式自适应**:根据聊天模式选择最优策略
|
||||||
|
|
||||||
|
### 第二层:动作激活内部优化
|
||||||
- **并行判定**:多个LLM判定任务并行执行
|
- **并行判定**:多个LLM判定任务并行执行
|
||||||
- **智能缓存**:相同上下文的判定结果缓存复用
|
- **智能缓存**:相同上下文的判定结果缓存复用
|
||||||
- **分层判定**:快速过滤 + 精确判定的两层架构
|
- **分层判定**:快速过滤 + 精确判定的两层架构
|
||||||
|
|
||||||
### 第二层:HFC流程级并行化
|
### 第三层:并行动作系统
|
||||||
|
- **并行执行**:支持动作与回复同时进行
|
||||||
|
- **用户体验**:减少等待时间,提升交互流畅性
|
||||||
|
- **灵活控制**:每个动作可独立配置并行行为
|
||||||
|
|
||||||
|
### 第四层:HFC流程级并行化
|
||||||
- **三阶段并行**:调整动作、回忆、处理器同时执行
|
- **三阶段并行**:调整动作、回忆、处理器同时执行
|
||||||
- **性能提升**:2.3x 理论加速比
|
- **性能提升**:2.3x 理论加速比
|
||||||
- **配置控制**:可根据环境灵活开启/关闭
|
- **配置控制**:可根据环境灵活开启/关闭
|
||||||
|
|
||||||
|
### 第五层:插件系统增强
|
||||||
|
- **enable_plugin**:精确控制插件启用状态
|
||||||
|
- **mode_enable**:支持模式级别的功能控制
|
||||||
|
- **向后兼容**:平滑迁移旧系统配置
|
||||||
|
|
||||||
### 综合效果
|
### 综合效果
|
||||||
- **响应速度**:显著提升机器人反应速度
|
- **响应速度**:显著提升机器人反应速度
|
||||||
- **成本优化**:减少不必要的LLM调用
|
- **成本优化**:减少不必要的LLM调用
|
||||||
- **智能决策**:四种激活类型覆盖所有场景
|
- **智能决策**:双激活类型覆盖所有场景
|
||||||
- **用户体验**:更快速、更智能的交互
|
- **用户体验**:更快速、更智能的交互
|
||||||
|
- **灵活配置**:精细化的功能控制
|
||||||
|
|
||||||
**总性能提升预估:3-5x**
|
**总性能提升预估:4-6x**
|
||||||
- 动作激活系统内部优化:1.5-2x
|
- 双激活类型系统:1.5x (Normal模式优化)
|
||||||
|
- 动作激活内部优化:1.5-2x
|
||||||
- HFC流程并行化:2.3x
|
- HFC流程并行化:2.3x
|
||||||
|
- 并行动作系统:额外30-50%提升
|
||||||
- 缓存和过滤优化:额外20-30%提升
|
- 缓存和过滤优化:额外20-30%提升
|
||||||
|
|
||||||
这使得MaiBot能够更快速、更智能地响应用户需求,提供卓越的交互体验。
|
这使得MaiBot能够更快速、更智能地响应用户需求,同时提供灵活的配置选项以适应不同的使用场景,实现了卓越的交互体验。
|
||||||
|
|
||||||
## 如何为Action添加激活类型
|
## 如何为Action添加激活类型
|
||||||
|
|
||||||
@@ -344,17 +652,24 @@ python test_parallel_optimization.py
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
|
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
|
||||||
|
from src.chat.chat_mode import ChatMode
|
||||||
|
|
||||||
@register_action
|
@register_action
|
||||||
class YourAction(BaseAction):
|
class YourAction(BaseAction):
|
||||||
action_name = "your_action"
|
action_name = "your_action"
|
||||||
action_description = "你的动作描述"
|
action_description = "你的动作描述"
|
||||||
|
|
||||||
# 设置激活类型 - 关键词触发示例
|
# 双激活类型配置
|
||||||
action_activation_type = ActionActivationType.KEYWORD
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
activation_keywords = ["关键词1", "关键词2", "keyword"]
|
activation_keywords = ["关键词1", "关键词2", "keyword"]
|
||||||
keyword_case_sensitive = False
|
keyword_case_sensitive = False
|
||||||
|
|
||||||
|
# 新增属性
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False
|
||||||
|
enable_plugin = True
|
||||||
|
|
||||||
# ... 其他代码
|
# ... 其他代码
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -362,48 +677,47 @@ class YourAction(BaseAction):
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
||||||
|
from src.chat.chat_mode import ChatMode
|
||||||
|
|
||||||
@register_action
|
@register_action
|
||||||
class YourPluginAction(PluginAction):
|
class YourPluginAction(PluginAction):
|
||||||
action_name = "your_plugin_action"
|
action_name = "your_plugin_action"
|
||||||
action_description = "你的插件动作描述"
|
action_description = "你的插件动作描述"
|
||||||
|
|
||||||
# 设置激活类型 - 关键词触发示例
|
# 双激活类型配置
|
||||||
action_activation_type = ActionActivationType.KEYWORD
|
focus_activation_type = ActionActivationType.KEYWORD
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
activation_keywords = ["触发词1", "trigger", "启动"]
|
activation_keywords = ["触发词1", "trigger", "启动"]
|
||||||
keyword_case_sensitive = False
|
keyword_case_sensitive = False
|
||||||
|
|
||||||
|
# 新增属性
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = True # 与回复并行执行
|
||||||
|
enable_plugin = True
|
||||||
|
|
||||||
# ... 其他代码
|
# ... 其他代码
|
||||||
```
|
```
|
||||||
|
|
||||||
## 现有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会遍历所有注册的动作
|
1. **ActionModifier处理**: 在planner运行前,ActionModifier会遍历所有注册的动作
|
||||||
2. **类型判断**: 根据每个动作的激活类型决定是否激活
|
2. **模式检查**: 根据当前聊天模式(Focus/Normal)和action的mode_enable进行过滤
|
||||||
3. **激活决策**:
|
3. **激活类型判断**: 根据当前模式选择对应的激活类型(focus_activation_type或normal_activation_type)
|
||||||
|
4. **激活决策**:
|
||||||
- ALWAYS: 直接激活
|
- ALWAYS: 直接激活
|
||||||
- RANDOM: 根据概率随机决定
|
- RANDOM: 根据概率随机决定
|
||||||
- LLM_JUDGE: 调用小模型判定
|
- LLM_JUDGE: 调用小模型判定(Normal模式会警告)
|
||||||
- KEYWORD: 检测关键词匹配
|
- KEYWORD: 检测关键词匹配
|
||||||
4. **结果收集**: 收集所有激活的动作供planner使用
|
5. **并行性检查**: 根据parallel_action决定是否与回复并行
|
||||||
|
6. **结果收集**: 收集所有激活的动作供planner使用
|
||||||
|
|
||||||
## 配置建议
|
## 配置建议
|
||||||
|
|
||||||
|
### 双激活类型策略选择
|
||||||
|
- **智能自适应(推荐)**: Focus用LLM_JUDGE,Normal用KEYWORD
|
||||||
|
- **性能优先**: 两个模式都用KEYWORD或RANDOM
|
||||||
|
- **功能专享**: 某些高级功能仅在Focus模式启用
|
||||||
|
|
||||||
### LLM判定提示词编写
|
### LLM判定提示词编写
|
||||||
- 明确指出激活条件和不激活条件
|
- 明确指出激活条件和不激活条件
|
||||||
- 使用简单清晰的语言
|
- 使用简单清晰的语言
|
||||||
@@ -423,6 +737,7 @@ class YourPluginAction(PluginAction):
|
|||||||
### 性能考虑
|
### 性能考虑
|
||||||
- LLM判定会增加响应时间,适度使用
|
- LLM判定会增加响应时间,适度使用
|
||||||
- 关键词检测性能最好,推荐优先使用
|
- 关键词检测性能最好,推荐优先使用
|
||||||
|
- Normal模式避免使用LLM_JUDGE
|
||||||
- 建议优先级:KEYWORD > ALWAYS > RANDOM > LLM_JUDGE
|
- 建议优先级:KEYWORD > ALWAYS > RANDOM > LLM_JUDGE
|
||||||
|
|
||||||
## 调试和测试
|
## 调试和测试
|
||||||
@@ -434,20 +749,25 @@ python test_action_activation.py
|
|||||||
```
|
```
|
||||||
|
|
||||||
该脚本会显示:
|
该脚本会显示:
|
||||||
- 所有注册动作的激活类型
|
- 所有注册动作的双激活类型配置
|
||||||
- 模拟不同消息下的激活结果
|
- 模拟不同模式下的激活结果
|
||||||
|
- 并行动作系统的工作状态
|
||||||
- 帮助验证配置是否正确
|
- 帮助验证配置是否正确
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **向后兼容**: 未设置激活类型的动作默认为ALWAYS
|
1. **重大变更**: `action_activation_type` 已被移除,必须使用双激活类型
|
||||||
2. **错误处理**: LLM判定失败时默认不激活该动作
|
2. **向后兼容**: 系统不再兼容旧的单一激活类型配置
|
||||||
3. **日志记录**: 系统会记录激活决策过程,便于调试
|
3. **错误处理**: LLM判定失败时默认不激活该动作
|
||||||
4. **性能影响**: LLM判定会略微增加响应时间
|
4. **性能警告**: Normal模式使用LLM_JUDGE会产生警告
|
||||||
|
5. **日志记录**: 系统会记录激活决策过程,便于调试
|
||||||
|
6. **性能影响**: LLM判定会略微增加响应时间
|
||||||
|
|
||||||
## 未来扩展
|
## 未来扩展
|
||||||
|
|
||||||
系统设计支持未来添加更多激活类型,如:
|
系统设计支持未来添加更多激活类型和功能,如:
|
||||||
- 基于时间的激活
|
- 基于时间的激活
|
||||||
- 基于用户权限的激活
|
- 基于用户权限的激活
|
||||||
- 基于群组设置的激活
|
- 基于群组设置的激活
|
||||||
|
- 基于对话历史的激活
|
||||||
|
- 基于情感状态的激活
|
||||||
@@ -304,7 +304,7 @@ class ExpressionLearner:
|
|||||||
# 如果没选够,随机补充
|
# 如果没选够,随机补充
|
||||||
if len(remove_set) < remove_count:
|
if len(remove_set) < remove_count:
|
||||||
remaining = set(indices) - remove_set
|
remaining = set(indices) - remove_set
|
||||||
remove_set.update(random.sample(remaining, remove_count - len(remove_set)))
|
remove_set.update(random.sample(list(remaining), remove_count - len(remove_set)))
|
||||||
|
|
||||||
remove_indices = list(remove_set)
|
remove_indices = list(remove_set)
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ def init_prompt():
|
|||||||
</调取记录>
|
</调取记录>
|
||||||
|
|
||||||
{name_block}
|
{name_block}
|
||||||
请你阅读聊天记录,查看是否需要调取某个人的信息。
|
请你阅读聊天记录,查看是否需要调取某个人的信息,这个人可以是出现在聊天记录中的,也可以是记录中提到的人。
|
||||||
你不同程度上认识群聊里的人,你可以根据聊天记录,回忆起有关他们的信息,帮助你参与聊天
|
你不同程度上认识群聊里的人,以及他们谈论到的人,你可以根据聊天记录,回忆起有关他们的信息,帮助你参与聊天
|
||||||
1.你需要提供用户名,以及你想要提取的信息名称类型来进行调取
|
1.你需要提供用户名,以及你想要提取的信息名称类型来进行调取
|
||||||
2.你也可以完全不输出任何信息
|
2.你也可以完全不输出任何信息
|
||||||
3.阅读调取记录,如果已经回忆过某个人的信息,请不要重复调取,除非你忘记了
|
3.阅读调取记录,如果已经回忆过某个人的信息,请不要重复调取,除非你忘记了
|
||||||
@@ -205,10 +205,10 @@ class RelationshipProcessor(BaseProcessor):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# logger.info(f"{self.log_prefix} 人物信息prompt: \n{prompt}\n")
|
logger.info(f"{self.log_prefix} 人物信息prompt: \n{prompt}\n")
|
||||||
content, _ = await self.llm_model.generate_response_async(prompt=prompt)
|
content, _ = await self.llm_model.generate_response_async(prompt=prompt)
|
||||||
if content:
|
if content:
|
||||||
# print(f"content: {content}")
|
print(f"content: {content}")
|
||||||
content_json = json.loads(repair_json(content))
|
content_json = json.loads(repair_json(content))
|
||||||
|
|
||||||
for person_name, info_type in content_json.items():
|
for person_name, info_type in content_json.items():
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ class ActionManager:
|
|||||||
|
|
||||||
# 初始化时将默认动作加载到使用中的动作
|
# 初始化时将默认动作加载到使用中的动作
|
||||||
self._using_actions = self._default_actions.copy()
|
self._using_actions = self._default_actions.copy()
|
||||||
|
|
||||||
|
# 添加系统核心动作
|
||||||
|
self._add_system_core_actions()
|
||||||
|
|
||||||
def _load_registered_actions(self) -> None:
|
def _load_registered_actions(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -59,14 +62,22 @@ class ActionManager:
|
|||||||
action_parameters: dict[str:str] = getattr(action_class, "action_parameters", {})
|
action_parameters: dict[str:str] = getattr(action_class, "action_parameters", {})
|
||||||
action_require: list[str] = getattr(action_class, "action_require", [])
|
action_require: list[str] = getattr(action_class, "action_require", [])
|
||||||
associated_types: list[str] = getattr(action_class, "associated_types", [])
|
associated_types: list[str] = getattr(action_class, "associated_types", [])
|
||||||
is_default: bool = getattr(action_class, "default", False)
|
is_enabled: bool = getattr(action_class, "enable_plugin", True)
|
||||||
|
|
||||||
# 获取激活类型相关属性
|
# 获取激活类型相关属性
|
||||||
activation_type: str = getattr(action_class, "action_activation_type", "always")
|
focus_activation_type: str = getattr(action_class, "focus_activation_type", "always")
|
||||||
|
normal_activation_type: str = getattr(action_class, "normal_activation_type", "always")
|
||||||
|
|
||||||
random_probability: float = getattr(action_class, "random_activation_probability", 0.3)
|
random_probability: float = getattr(action_class, "random_activation_probability", 0.3)
|
||||||
llm_judge_prompt: str = getattr(action_class, "llm_judge_prompt", "")
|
llm_judge_prompt: str = getattr(action_class, "llm_judge_prompt", "")
|
||||||
activation_keywords: list[str] = getattr(action_class, "activation_keywords", [])
|
activation_keywords: list[str] = getattr(action_class, "activation_keywords", [])
|
||||||
keyword_case_sensitive: bool = getattr(action_class, "keyword_case_sensitive", False)
|
keyword_case_sensitive: bool = getattr(action_class, "keyword_case_sensitive", False)
|
||||||
|
|
||||||
|
# 获取模式启用属性
|
||||||
|
mode_enable: str = getattr(action_class, "mode_enable", "all")
|
||||||
|
|
||||||
|
# 获取并行执行属性
|
||||||
|
parallel_action: bool = getattr(action_class, "parallel_action", False)
|
||||||
|
|
||||||
if action_name and action_description:
|
if action_name and action_description:
|
||||||
# 创建动作信息字典
|
# 创建动作信息字典
|
||||||
@@ -75,18 +86,21 @@ class ActionManager:
|
|||||||
"parameters": action_parameters,
|
"parameters": action_parameters,
|
||||||
"require": action_require,
|
"require": action_require,
|
||||||
"associated_types": associated_types,
|
"associated_types": associated_types,
|
||||||
"activation_type": activation_type,
|
"focus_activation_type": focus_activation_type,
|
||||||
|
"normal_activation_type": normal_activation_type,
|
||||||
"random_probability": random_probability,
|
"random_probability": random_probability,
|
||||||
"llm_judge_prompt": llm_judge_prompt,
|
"llm_judge_prompt": llm_judge_prompt,
|
||||||
"activation_keywords": activation_keywords,
|
"activation_keywords": activation_keywords,
|
||||||
"keyword_case_sensitive": keyword_case_sensitive,
|
"keyword_case_sensitive": keyword_case_sensitive,
|
||||||
|
"mode_enable": mode_enable,
|
||||||
|
"parallel_action": parallel_action,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 添加到所有已注册的动作
|
# 添加到所有已注册的动作
|
||||||
self._registered_actions[action_name] = action_info
|
self._registered_actions[action_name] = action_info
|
||||||
|
|
||||||
# 添加到默认动作(如果是默认动作)
|
# 添加到默认动作(如果启用插件)
|
||||||
if is_default:
|
if is_enabled:
|
||||||
self._default_actions[action_name] = action_info
|
self._default_actions[action_name] = action_info
|
||||||
|
|
||||||
# logger.info(f"所有注册动作: {list(self._registered_actions.keys())}")
|
# logger.info(f"所有注册动作: {list(self._registered_actions.keys())}")
|
||||||
@@ -212,9 +226,34 @@ class ActionManager:
|
|||||||
return self._default_actions.copy()
|
return self._default_actions.copy()
|
||||||
|
|
||||||
def get_using_actions(self) -> Dict[str, ActionInfo]:
|
def get_using_actions(self) -> Dict[str, ActionInfo]:
|
||||||
"""获取当前正在使用的动作集"""
|
"""获取当前正在使用的动作集合"""
|
||||||
return self._using_actions.copy()
|
return self._using_actions.copy()
|
||||||
|
|
||||||
|
def get_using_actions_for_mode(self, mode: str) -> Dict[str, ActionInfo]:
|
||||||
|
"""
|
||||||
|
根据聊天模式获取可用的动作集合
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode: 聊天模式 ("focus", "normal", "all")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, ActionInfo]: 在指定模式下可用的动作集合
|
||||||
|
"""
|
||||||
|
filtered_actions = {}
|
||||||
|
|
||||||
|
for action_name, action_info in self._using_actions.items():
|
||||||
|
action_mode = action_info.get("mode_enable", "all")
|
||||||
|
|
||||||
|
# 检查动作是否在当前模式下启用
|
||||||
|
if action_mode == "all" or action_mode == mode:
|
||||||
|
filtered_actions[action_name] = action_info
|
||||||
|
logger.debug(f"动作 {action_name} 在模式 {mode} 下可用 (mode_enable: {action_mode})")
|
||||||
|
else:
|
||||||
|
logger.debug(f"动作 {action_name} 在模式 {mode} 下不可用 (mode_enable: {action_mode})")
|
||||||
|
|
||||||
|
logger.info(f"模式 {mode} 下可用动作: {list(filtered_actions.keys())}")
|
||||||
|
return filtered_actions
|
||||||
|
|
||||||
def add_action_to_using(self, action_name: str) -> bool:
|
def add_action_to_using(self, action_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
添加已注册的动作到当前使用的动作集
|
添加已注册的动作到当前使用的动作集
|
||||||
@@ -306,6 +345,36 @@ class ActionManager:
|
|||||||
def restore_default_actions(self) -> None:
|
def restore_default_actions(self) -> None:
|
||||||
"""恢复默认动作集到使用集"""
|
"""恢复默认动作集到使用集"""
|
||||||
self._using_actions = self._default_actions.copy()
|
self._using_actions = self._default_actions.copy()
|
||||||
|
# 添加系统核心动作(即使enable_plugin为False的系统动作)
|
||||||
|
self._add_system_core_actions()
|
||||||
|
|
||||||
|
def _add_system_core_actions(self) -> None:
|
||||||
|
"""
|
||||||
|
添加系统核心动作到使用集
|
||||||
|
系统核心动作是那些enable_plugin为False但是系统必需的动作
|
||||||
|
"""
|
||||||
|
system_core_actions = ["exit_focus_chat"] # 可以根据需要扩展
|
||||||
|
|
||||||
|
for action_name in system_core_actions:
|
||||||
|
if action_name in self._registered_actions and action_name not in self._using_actions:
|
||||||
|
self._using_actions[action_name] = self._registered_actions[action_name]
|
||||||
|
logger.info(f"添加系统核心动作到使用集: {action_name}")
|
||||||
|
|
||||||
|
def add_system_action_if_needed(self, action_name: str) -> bool:
|
||||||
|
"""
|
||||||
|
根据需要添加系统动作到使用集
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action_name: 动作名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功添加
|
||||||
|
"""
|
||||||
|
if action_name in self._registered_actions and action_name not in self._using_actions:
|
||||||
|
self._using_actions[action_name] = self._registered_actions[action_name]
|
||||||
|
logger.info(f"临时添加系统动作到使用集: {action_name}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def get_action(self, action_name: str) -> Optional[Type[BaseAction]]:
|
def get_action(self, action_name: str) -> Optional[Type[BaseAction]]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
from . import reply_action # noqa
|
from . import reply_action # noqa
|
||||||
from . import no_reply_action # noqa
|
from . import no_reply_action # noqa
|
||||||
from . import exit_focus_chat_action # noqa
|
from . import exit_focus_chat_action # noqa
|
||||||
|
from . import emoji_action # noqa
|
||||||
|
|
||||||
# 在此处添加更多动作模块导入
|
# 在此处添加更多动作模块导入
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ class ActionActivationType:
|
|||||||
RANDOM = "random" # 随机启用action到planner
|
RANDOM = "random" # 随机启用action到planner
|
||||||
KEYWORD = "keyword" # 关键词触发启用action到planner
|
KEYWORD = "keyword" # 关键词触发启用action到planner
|
||||||
|
|
||||||
|
# 聊天模式枚举
|
||||||
|
class ChatMode:
|
||||||
|
FOCUS = "focus" # Focus聊天模式
|
||||||
|
NORMAL = "normal" # Normal聊天模式
|
||||||
|
ALL = "all" # 所有聊天模式
|
||||||
|
|
||||||
def register_action(cls):
|
def register_action(cls):
|
||||||
"""
|
"""
|
||||||
动作注册装饰器
|
动作注册装饰器
|
||||||
@@ -24,7 +30,10 @@ def register_action(cls):
|
|||||||
class MyAction(BaseAction):
|
class MyAction(BaseAction):
|
||||||
action_name = "my_action"
|
action_name = "my_action"
|
||||||
action_description = "我的动作"
|
action_description = "我的动作"
|
||||||
action_activation_type = ActionActivationType.ALWAYS
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
parallel_action = False
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
# 检查类是否有必要的属性
|
# 检查类是否有必要的属性
|
||||||
@@ -34,7 +43,7 @@ def register_action(cls):
|
|||||||
|
|
||||||
action_name = cls.action_name
|
action_name = cls.action_name
|
||||||
action_description = cls.action_description
|
action_description = cls.action_description
|
||||||
is_default = getattr(cls, "default", False)
|
is_enabled = getattr(cls, "enable_plugin", True) # 默认启用插件
|
||||||
|
|
||||||
if not action_name or not action_description:
|
if not action_name or not action_description:
|
||||||
logger.error(f"动作类 {cls.__name__} 的 action_name 或 action_description 为空")
|
logger.error(f"动作类 {cls.__name__} 的 action_name 或 action_description 为空")
|
||||||
@@ -43,11 +52,11 @@ def register_action(cls):
|
|||||||
# 将动作类注册到全局注册表
|
# 将动作类注册到全局注册表
|
||||||
_ACTION_REGISTRY[action_name] = cls
|
_ACTION_REGISTRY[action_name] = cls
|
||||||
|
|
||||||
# 如果是默认动作,添加到默认动作集
|
# 如果启用插件,添加到默认动作集
|
||||||
if is_default:
|
if is_enabled:
|
||||||
_DEFAULT_ACTIONS[action_name] = action_description
|
_DEFAULT_ACTIONS[action_name] = action_description
|
||||||
|
|
||||||
logger.info(f"已注册动作: {action_name} -> {cls.__name__},默认: {is_default}")
|
logger.info(f"已注册动作: {action_name} -> {cls.__name__},插件启用: {is_enabled}")
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
|
||||||
@@ -73,20 +82,32 @@ class BaseAction(ABC):
|
|||||||
self.action_parameters: dict = {}
|
self.action_parameters: dict = {}
|
||||||
self.action_require: list[str] = []
|
self.action_require: list[str] = []
|
||||||
|
|
||||||
# 动作激活类型,默认为always
|
# 动作激活类型设置
|
||||||
self.action_activation_type: str = ActionActivationType.ALWAYS
|
# Focus模式下的激活类型,默认为always
|
||||||
# 随机激活的概率(0.0-1.0),仅当activation_type为random时有效
|
self.focus_activation_type: str = ActionActivationType.ALWAYS
|
||||||
|
# Normal模式下的激活类型,默认为always
|
||||||
|
self.normal_activation_type: str = ActionActivationType.ALWAYS
|
||||||
|
|
||||||
|
# 随机激活的概率(0.0-1.0),用于RANDOM激活类型
|
||||||
self.random_activation_probability: float = 0.3
|
self.random_activation_probability: float = 0.3
|
||||||
# LLM判定的提示词,仅当activation_type为llm_judge时有效
|
# LLM判定的提示词,用于LLM_JUDGE激活类型
|
||||||
self.llm_judge_prompt: str = ""
|
self.llm_judge_prompt: str = ""
|
||||||
# 关键词触发列表,仅当activation_type为keyword时有效
|
# 关键词触发列表,用于KEYWORD激活类型
|
||||||
self.activation_keywords: list[str] = []
|
self.activation_keywords: list[str] = []
|
||||||
# 关键词匹配是否区分大小写
|
# 关键词匹配是否区分大小写
|
||||||
self.keyword_case_sensitive: bool = False
|
self.keyword_case_sensitive: bool = False
|
||||||
|
|
||||||
|
# 模式启用设置:指定在哪些聊天模式下启用此动作
|
||||||
|
# 可选值: "focus"(仅Focus模式), "normal"(仅Normal模式), "all"(所有模式)
|
||||||
|
self.mode_enable: str = ChatMode.ALL
|
||||||
|
|
||||||
|
# 并行执行设置:仅在Normal模式下生效,设置为True的动作可以与回复动作并行执行
|
||||||
|
# 而不是替代回复动作,适用于图片生成、TTS、禁言等不需要覆盖回复的动作
|
||||||
|
self.parallel_action: bool = False
|
||||||
|
|
||||||
self.associated_types: list[str] = []
|
self.associated_types: list[str] = []
|
||||||
|
|
||||||
self.default: bool = False
|
self.enable_plugin: bool = True # 是否启用插件,默认启用
|
||||||
|
|
||||||
self.action_data = action_data
|
self.action_data = action_data
|
||||||
self.reasoning = reasoning
|
self.reasoning = reasoning
|
||||||
|
|||||||
150
src/chat/focus_chat/planners/actions/emoji_action.py
Normal file
150
src/chat/focus_chat/planners/actions/emoji_action.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
from src.common.logger_manager import get_logger
|
||||||
|
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode
|
||||||
|
from typing import Tuple, List
|
||||||
|
from src.chat.heart_flow.observation.observation import Observation
|
||||||
|
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
|
||||||
|
from src.chat.message_receive.chat_stream import ChatStream
|
||||||
|
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
|
||||||
|
from src.config.config import global_config
|
||||||
|
|
||||||
|
logger = get_logger("action_taken")
|
||||||
|
|
||||||
|
|
||||||
|
@register_action
|
||||||
|
class EmojiAction(BaseAction):
|
||||||
|
"""表情动作处理类
|
||||||
|
|
||||||
|
处理构建和发送消息表情的动作。
|
||||||
|
"""
|
||||||
|
|
||||||
|
action_name: str = "emoji"
|
||||||
|
action_description: str = "当你想单独发送一个表情包辅助你的回复表达"
|
||||||
|
action_parameters: dict[str:str] = {
|
||||||
|
"description": "文字描述你想要发送的表情包内容",
|
||||||
|
}
|
||||||
|
action_require: list[str] = [
|
||||||
|
"表达情绪时可以选择使用",
|
||||||
|
"重点:不要连续发,如果你已经发过[表情包],就不要选择此动作"]
|
||||||
|
|
||||||
|
associated_types: list[str] = ["emoji"]
|
||||||
|
|
||||||
|
enable_plugin = True
|
||||||
|
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM
|
||||||
|
|
||||||
|
random_activation_probability = global_config.normal_chat.emoji_chance
|
||||||
|
|
||||||
|
parallel_action = True
|
||||||
|
|
||||||
|
|
||||||
|
llm_judge_prompt = """
|
||||||
|
判定是否需要使用表情动作的条件:
|
||||||
|
1. 用户明确要求使用表情包
|
||||||
|
2. 这是一个适合表达强烈情绪的场合
|
||||||
|
3. 不要发送太多表情包,如果你已经发送过多个表情包
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 模式启用设置 - 表情动作只在Focus模式下使用
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
action_data: dict,
|
||||||
|
reasoning: str,
|
||||||
|
cycle_timers: dict,
|
||||||
|
thinking_id: str,
|
||||||
|
observations: List[Observation],
|
||||||
|
chat_stream: ChatStream,
|
||||||
|
log_prefix: str,
|
||||||
|
replyer: DefaultReplyer,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
"""初始化回复动作处理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action_name: 动作名称
|
||||||
|
action_data: 动作数据,包含 message, emojis, target 等
|
||||||
|
reasoning: 执行该动作的理由
|
||||||
|
cycle_timers: 计时器字典
|
||||||
|
thinking_id: 思考ID
|
||||||
|
observations: 观察列表
|
||||||
|
replyer: 回复器
|
||||||
|
chat_stream: 聊天流
|
||||||
|
log_prefix: 日志前缀
|
||||||
|
"""
|
||||||
|
super().__init__(action_data, reasoning, cycle_timers, thinking_id)
|
||||||
|
self.observations = observations
|
||||||
|
self.replyer = replyer
|
||||||
|
self.chat_stream = chat_stream
|
||||||
|
self.log_prefix = log_prefix
|
||||||
|
|
||||||
|
async def handle_action(self) -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
处理回复动作
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[bool, str]: (是否执行成功, 回复文本)
|
||||||
|
"""
|
||||||
|
# 注意: 此处可能会使用不同的expressor实现根据任务类型切换不同的回复策略
|
||||||
|
return await self._handle_reply(
|
||||||
|
reasoning=self.reasoning,
|
||||||
|
reply_data=self.action_data,
|
||||||
|
cycle_timers=self.cycle_timers,
|
||||||
|
thinking_id=self.thinking_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _handle_reply(
|
||||||
|
self, reasoning: str, reply_data: dict, cycle_timers: dict, thinking_id: str
|
||||||
|
) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
处理统一的回复动作 - 可包含文本和表情,顺序任意
|
||||||
|
|
||||||
|
reply_data格式:
|
||||||
|
{
|
||||||
|
"description": "描述你想要发送的表情"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
logger.info(f"{self.log_prefix} 决定发送表情")
|
||||||
|
# 从聊天观察获取锚定消息
|
||||||
|
# chatting_observation: ChattingObservation = next(
|
||||||
|
# obs for obs in self.observations if isinstance(obs, ChattingObservation)
|
||||||
|
# )
|
||||||
|
# if reply_data.get("target"):
|
||||||
|
# anchor_message = chatting_observation.search_message_by_text(reply_data["target"])
|
||||||
|
# else:
|
||||||
|
# anchor_message = None
|
||||||
|
|
||||||
|
# 如果没有找到锚点消息,创建一个占位符
|
||||||
|
# if not anchor_message:
|
||||||
|
# logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符")
|
||||||
|
# anchor_message = await create_empty_anchor_message(
|
||||||
|
# self.chat_stream.platform, self.chat_stream.group_info, self.chat_stream
|
||||||
|
# )
|
||||||
|
# else:
|
||||||
|
# anchor_message.update_chat_stream(self.chat_stream)
|
||||||
|
|
||||||
|
logger.info(f"{self.log_prefix} 为了表情包创建占位符")
|
||||||
|
anchor_message = await create_empty_anchor_message(
|
||||||
|
self.chat_stream.platform, self.chat_stream.group_info, self.chat_stream
|
||||||
|
)
|
||||||
|
|
||||||
|
success, reply_set = await self.replyer.deal_emoji(
|
||||||
|
cycle_timers=cycle_timers,
|
||||||
|
action_data=reply_data,
|
||||||
|
anchor_message=anchor_message,
|
||||||
|
# reasoning=reasoning,
|
||||||
|
thinking_id=thinking_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
reply_text = ""
|
||||||
|
if reply_set:
|
||||||
|
for reply in reply_set:
|
||||||
|
type = reply[0]
|
||||||
|
data = reply[1]
|
||||||
|
if type == "text":
|
||||||
|
reply_text += data
|
||||||
|
elif type == "emoji":
|
||||||
|
reply_text += data
|
||||||
|
|
||||||
|
return success, reply_text
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
from src.common.logger_manager import get_logger
|
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, ChatMode
|
||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
from src.chat.heart_flow.observation.observation import Observation
|
from src.chat.heart_flow.observation.observation import Observation
|
||||||
from src.chat.message_receive.chat_stream import ChatStream
|
from src.chat.message_receive.chat_stream import ChatStream
|
||||||
@@ -25,7 +25,11 @@ class ExitFocusChatAction(BaseAction):
|
|||||||
"当前内容不需要持续专注关注,你决定退出专注聊天",
|
"当前内容不需要持续专注关注,你决定退出专注聊天",
|
||||||
"聊天内容已经完成,你决定退出专注聊天",
|
"聊天内容已经完成,你决定退出专注聊天",
|
||||||
]
|
]
|
||||||
default = False
|
# 退出专注聊天是系统核心功能,不是插件,但默认不启用(需要特定条件触发)
|
||||||
|
enable_plugin = False
|
||||||
|
|
||||||
|
# 模式启用设置 - 退出专注聊天动作只在Focus模式下使用
|
||||||
|
mode_enable = ChatMode.FOCUS
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import traceback
|
import traceback
|
||||||
from src.common.logger_manager import get_logger
|
from src.common.logger_manager import get_logger
|
||||||
from src.chat.utils.timer_calculator import Timer
|
from src.chat.utils.timer_calculator import Timer
|
||||||
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
|
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode
|
||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
from src.chat.heart_flow.observation.observation import Observation
|
from src.chat.heart_flow.observation.observation import Observation
|
||||||
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
|
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
|
||||||
@@ -28,10 +28,13 @@ class NoReplyAction(BaseAction):
|
|||||||
"你连续发送了太多消息,且无人回复",
|
"你连续发送了太多消息,且无人回复",
|
||||||
"想要休息一下",
|
"想要休息一下",
|
||||||
]
|
]
|
||||||
default = True
|
enable_plugin = True
|
||||||
|
|
||||||
# 激活类型设置
|
# 激活类型设置
|
||||||
action_activation_type = ActionActivationType.ALWAYS
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
|
||||||
|
# 模式启用设置 - no_reply动作只在Focus模式下使用
|
||||||
|
mode_enable = ChatMode.FOCUS
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import traceback
|
import traceback
|
||||||
from typing import Tuple, Dict, List, Any, Optional, Union, Type
|
from typing import Tuple, Dict, List, Any, Optional, Union, Type
|
||||||
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType # noqa F401
|
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode # noqa F401
|
||||||
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
|
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
|
||||||
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
|
from src.chat.focus_chat.hfc_utils import create_empty_anchor_message
|
||||||
from src.common.logger_manager import get_logger
|
from src.common.logger_manager import get_logger
|
||||||
@@ -35,11 +35,15 @@ class PluginAction(BaseAction):
|
|||||||
action_config_file_name: Optional[str] = None # 插件可以覆盖此属性来指定配置文件名
|
action_config_file_name: Optional[str] = None # 插件可以覆盖此属性来指定配置文件名
|
||||||
|
|
||||||
# 默认激活类型设置,插件可以覆盖
|
# 默认激活类型设置,插件可以覆盖
|
||||||
action_activation_type = ActionActivationType.ALWAYS
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
normal_activation_type = ActionActivationType.ALWAYS
|
||||||
random_activation_probability: float = 0.3
|
random_activation_probability: float = 0.3
|
||||||
llm_judge_prompt: str = ""
|
llm_judge_prompt: str = ""
|
||||||
activation_keywords: list[str] = []
|
activation_keywords: list[str] = []
|
||||||
keyword_case_sensitive: bool = False
|
keyword_case_sensitive: bool = False
|
||||||
|
|
||||||
|
# 默认模式启用设置 - 插件动作默认在所有模式下可用,插件可以覆盖
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from src.common.logger_manager import get_logger
|
from src.common.logger_manager import get_logger
|
||||||
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType
|
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode
|
||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
from src.chat.heart_flow.observation.observation import Observation
|
from src.chat.heart_flow.observation.observation import Observation
|
||||||
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
|
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
|
||||||
@@ -26,21 +26,23 @@ class ReplyAction(BaseAction):
|
|||||||
action_name: str = "reply"
|
action_name: str = "reply"
|
||||||
action_description: str = "当你想要参与回复或者聊天"
|
action_description: str = "当你想要参与回复或者聊天"
|
||||||
action_parameters: dict[str:str] = {
|
action_parameters: dict[str:str] = {
|
||||||
"reply_to": "如果是明确回复某个人的发言,请在reply_to参数中指定,格式:(用户名:发言内容),如果不是,reply_to的值设为none",
|
"reply_to": "如果是明确回复某个人的发言,请在reply_to参数中指定,格式:(用户名:发言内容),如果不是,reply_to的值设为none"
|
||||||
"emoji": "如果你想用表情包辅助你的回答,请在emoji参数中用文字描述你想要发送的表情包内容,如果没有,值设为空",
|
|
||||||
}
|
}
|
||||||
action_require: list[str] = [
|
action_require: list[str] = [
|
||||||
"你想要闲聊或者随便附和",
|
"你想要闲聊或者随便附和",
|
||||||
"有人提到你",
|
"有人提到你",
|
||||||
"如果你刚刚回复,不要对同一个话题重复回应"
|
"如果你刚刚进行了回复,不要对同一个话题重复回应"
|
||||||
]
|
]
|
||||||
|
|
||||||
associated_types: list[str] = ["text", "emoji"]
|
associated_types: list[str] = ["text"]
|
||||||
|
|
||||||
default = True
|
enable_plugin = True
|
||||||
|
|
||||||
# 激活类型设置
|
# 激活类型设置
|
||||||
action_activation_type = ActionActivationType.ALWAYS
|
focus_activation_type = ActionActivationType.ALWAYS
|
||||||
|
|
||||||
|
# 模式启用设置 - 回复动作只在Focus模式下使用
|
||||||
|
mode_enable = ChatMode.FOCUS
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -105,7 +107,6 @@ class ReplyAction(BaseAction):
|
|||||||
{
|
{
|
||||||
"text": "你好啊" # 文本内容列表(可选)
|
"text": "你好啊" # 文本内容列表(可选)
|
||||||
"target": "锚定消息", # 锚定消息的文本内容
|
"target": "锚定消息", # 锚定消息的文本内容
|
||||||
"emojis": "微笑" # 表情关键词列表(可选)
|
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
logger.info(f"{self.log_prefix} 决定回复: {self.reasoning}")
|
logger.info(f"{self.log_prefix} 决定回复: {self.reasoning}")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from src.chat.heart_flow.observation.chatting_observation import ChattingObserva
|
|||||||
from src.chat.message_receive.chat_stream import chat_manager
|
from src.chat.message_receive.chat_stream import chat_manager
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.llm_models.utils_model import LLMRequest
|
from src.llm_models.utils_model import LLMRequest
|
||||||
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType
|
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType, ChatMode
|
||||||
import random
|
import random
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -29,7 +29,7 @@ class ActionModifier:
|
|||||||
def __init__(self, action_manager: ActionManager):
|
def __init__(self, action_manager: ActionManager):
|
||||||
"""初始化动作处理器"""
|
"""初始化动作处理器"""
|
||||||
self.action_manager = action_manager
|
self.action_manager = action_manager
|
||||||
self.all_actions = self.action_manager.get_registered_actions()
|
self.all_actions = self.action_manager.get_using_actions_for_mode(ChatMode.FOCUS)
|
||||||
|
|
||||||
# 用于LLM判定的小模型
|
# 用于LLM判定的小模型
|
||||||
self.llm_judge = LLMRequest(
|
self.llm_judge = LLMRequest(
|
||||||
@@ -78,7 +78,8 @@ class ActionModifier:
|
|||||||
# 处理HFCloopObservation - 传统的循环历史分析
|
# 处理HFCloopObservation - 传统的循环历史分析
|
||||||
if hfc_obs:
|
if hfc_obs:
|
||||||
obs = hfc_obs
|
obs = hfc_obs
|
||||||
all_actions = self.all_actions
|
# 获取适用于FOCUS模式的动作
|
||||||
|
all_actions = self.action_manager.get_using_actions_for_mode(ChatMode.FOCUS)
|
||||||
action_changes = await self.analyze_loop_actions(obs)
|
action_changes = await self.analyze_loop_actions(obs)
|
||||||
if action_changes["add"] or action_changes["remove"]:
|
if action_changes["add"] or action_changes["remove"]:
|
||||||
# 合并动作变更
|
# 合并动作变更
|
||||||
@@ -129,9 +130,9 @@ class ActionModifier:
|
|||||||
if chat_content is not None:
|
if chat_content is not None:
|
||||||
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
|
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
|
||||||
|
|
||||||
# 获取当前使用的动作集(经过第一阶段处理)
|
# 获取当前使用的动作集(经过第一阶段处理,且适用于FOCUS模式)
|
||||||
current_using_actions = self.action_manager.get_using_actions()
|
current_using_actions = self.action_manager.get_using_actions()
|
||||||
all_registered_actions = self.action_manager.get_registered_actions()
|
all_registered_actions = self.action_manager.get_using_actions_for_mode(ChatMode.FOCUS)
|
||||||
|
|
||||||
# 构建完整的动作信息
|
# 构建完整的动作信息
|
||||||
current_actions_with_info = {}
|
current_actions_with_info = {}
|
||||||
@@ -157,7 +158,7 @@ class ActionModifier:
|
|||||||
# 确定移除原因
|
# 确定移除原因
|
||||||
if action_name in all_registered_actions:
|
if action_name in all_registered_actions:
|
||||||
action_info = all_registered_actions[action_name]
|
action_info = all_registered_actions[action_name]
|
||||||
activation_type = action_info.get("activation_type", ActionActivationType.ALWAYS)
|
activation_type = action_info.get("focus_activation_type", ActionActivationType.ALWAYS)
|
||||||
|
|
||||||
if activation_type == ActionActivationType.RANDOM:
|
if activation_type == ActionActivationType.RANDOM:
|
||||||
probability = action_info.get("random_probability", 0.3)
|
probability = action_info.get("random_probability", 0.3)
|
||||||
@@ -207,7 +208,7 @@ class ActionModifier:
|
|||||||
keyword_actions = {}
|
keyword_actions = {}
|
||||||
|
|
||||||
for action_name, action_info in actions_with_info.items():
|
for action_name, action_info in actions_with_info.items():
|
||||||
activation_type = action_info.get("activation_type", ActionActivationType.ALWAYS)
|
activation_type = action_info.get("focus_activation_type", ActionActivationType.ALWAYS)
|
||||||
|
|
||||||
if activation_type == ActionActivationType.ALWAYS:
|
if activation_type == ActionActivationType.ALWAYS:
|
||||||
always_actions[action_name] = action_info
|
always_actions[action_name] = action_info
|
||||||
@@ -433,6 +434,7 @@ class ActionModifier:
|
|||||||
action_require = action_info.get("require", [])
|
action_require = action_info.get("require", [])
|
||||||
custom_prompt = action_info.get("llm_judge_prompt", "")
|
custom_prompt = action_info.get("llm_judge_prompt", "")
|
||||||
|
|
||||||
|
|
||||||
# 构建基础判定提示词
|
# 构建基础判定提示词
|
||||||
base_prompt = f"""
|
base_prompt = f"""
|
||||||
你需要判断在当前聊天情况下,是否应该激活名为"{action_name}"的动作。
|
你需要判断在当前聊天情况下,是否应该激活名为"{action_name}"的动作。
|
||||||
@@ -462,7 +464,7 @@ class ActionModifier:
|
|||||||
# 解析响应
|
# 解析响应
|
||||||
response = response.strip().lower()
|
response = response.strip().lower()
|
||||||
|
|
||||||
print(base_prompt)
|
# print(base_prompt)
|
||||||
print(f"LLM判定动作 {action_name}:响应='{response}'")
|
print(f"LLM判定动作 {action_name}:响应='{response}'")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
|||||||
from src.individuality.individuality import individuality
|
from src.individuality.individuality import individuality
|
||||||
from src.chat.focus_chat.planners.action_manager import ActionManager
|
from src.chat.focus_chat.planners.action_manager import ActionManager
|
||||||
from src.chat.focus_chat.planners.modify_actions import ActionModifier
|
from src.chat.focus_chat.planners.modify_actions import ActionModifier
|
||||||
|
from src.chat.focus_chat.planners.actions.base_action import ChatMode
|
||||||
from json_repair import repair_json
|
from json_repair import repair_json
|
||||||
from src.chat.focus_chat.planners.base_planner import BasePlanner
|
from src.chat.focus_chat.planners.base_planner import BasePlanner
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -144,7 +145,8 @@ class ActionPlanner(BasePlanner):
|
|||||||
|
|
||||||
# 获取经过modify_actions处理后的最终可用动作集
|
# 获取经过modify_actions处理后的最终可用动作集
|
||||||
# 注意:动作的激活判定现在在主循环的modify_actions中完成
|
# 注意:动作的激活判定现在在主循环的modify_actions中完成
|
||||||
current_available_actions_dict = self.action_manager.get_using_actions()
|
# 使用Focus模式过滤动作
|
||||||
|
current_available_actions_dict = self.action_manager.get_using_actions_for_mode(ChatMode.FOCUS)
|
||||||
|
|
||||||
# 获取完整的动作信息
|
# 获取完整的动作信息
|
||||||
all_registered_actions = self.action_manager.get_registered_actions()
|
all_registered_actions = self.action_manager.get_registered_actions()
|
||||||
|
|||||||
@@ -150,17 +150,6 @@ class DefaultReplyer:
|
|||||||
action_data=action_data,
|
action_data=action_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
with Timer("选择表情", cycle_timers):
|
|
||||||
emoji_keyword = action_data.get("emoji", "")
|
|
||||||
print(f"emoji_keyword: {emoji_keyword}")
|
|
||||||
if emoji_keyword:
|
|
||||||
emoji_base64, _description, _emotion = await self._choose_emoji(emoji_keyword)
|
|
||||||
# print(f"emoji_base64: {emoji_base64}")
|
|
||||||
# print(f"emoji_description: {_description}")
|
|
||||||
# print(f"emoji_emotion: {emotion}")
|
|
||||||
if emoji_base64:
|
|
||||||
reply.append(("emoji", emoji_base64))
|
|
||||||
|
|
||||||
if reply:
|
if reply:
|
||||||
with Timer("发送消息", cycle_timers):
|
with Timer("发送消息", cycle_timers):
|
||||||
sent_msg_list = await self.send_response_messages(
|
sent_msg_list = await self.send_response_messages(
|
||||||
|
|||||||
@@ -280,28 +280,26 @@ class NormalChat:
|
|||||||
info_catcher = info_catcher_manager.get_info_catcher(thinking_id)
|
info_catcher = info_catcher_manager.get_info_catcher(thinking_id)
|
||||||
info_catcher.catch_decide_to_response(message)
|
info_catcher.catch_decide_to_response(message)
|
||||||
|
|
||||||
|
# 如果启用planner,预先修改可用actions(避免在并行任务中重复调用)
|
||||||
|
available_actions = None
|
||||||
|
if self.enable_planner:
|
||||||
|
try:
|
||||||
|
await self.action_modifier.modify_actions_for_normal_chat(
|
||||||
|
self.chat_stream, self.recent_replies, message.processed_plain_text
|
||||||
|
)
|
||||||
|
available_actions = self.action_manager.get_using_actions()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}")
|
||||||
|
available_actions = None
|
||||||
|
|
||||||
# 定义并行执行的任务
|
# 定义并行执行的任务
|
||||||
async def generate_normal_response():
|
async def generate_normal_response():
|
||||||
"""生成普通回复"""
|
"""生成普通回复"""
|
||||||
try:
|
try:
|
||||||
# 如果启用planner,获取可用actions
|
|
||||||
enable_planner = self.enable_planner
|
|
||||||
available_actions = None
|
|
||||||
|
|
||||||
if enable_planner:
|
|
||||||
try:
|
|
||||||
await self.action_modifier.modify_actions_for_normal_chat(
|
|
||||||
self.chat_stream, self.recent_replies
|
|
||||||
)
|
|
||||||
available_actions = self.action_manager.get_using_actions()
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}")
|
|
||||||
available_actions = None
|
|
||||||
|
|
||||||
return await self.gpt.generate_response(
|
return await self.gpt.generate_response(
|
||||||
message=message,
|
message=message,
|
||||||
thinking_id=thinking_id,
|
thinking_id=thinking_id,
|
||||||
enable_planner=enable_planner,
|
enable_planner=self.enable_planner,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -315,38 +313,37 @@ class NormalChat:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 并行执行动作修改和规划准备
|
# 获取发送者名称(动作修改已在并行执行前完成)
|
||||||
async def modify_actions():
|
sender_name = self._get_sender_name(message)
|
||||||
"""修改可用动作集合"""
|
|
||||||
return await self.action_modifier.modify_actions_for_normal_chat(
|
no_action = {
|
||||||
self.chat_stream, self.recent_replies
|
"action_result": {"action_type": "no_action", "action_data": {}, "reasoning": "规划器初始化默认", "is_parallel": True},
|
||||||
)
|
"chat_context": "",
|
||||||
|
"action_prompt": "",
|
||||||
async def prepare_planning():
|
}
|
||||||
"""准备规划所需的信息"""
|
|
||||||
return self._get_sender_name(message)
|
|
||||||
|
|
||||||
# 并行执行动作修改和准备工作
|
|
||||||
_, sender_name = await asyncio.gather(modify_actions(), prepare_planning())
|
|
||||||
|
|
||||||
# 检查是否应该跳过规划
|
# 检查是否应该跳过规划
|
||||||
if self.action_modifier.should_skip_planning():
|
if self.action_modifier.should_skip_planning():
|
||||||
logger.debug(f"[{self.stream_name}] 没有可用动作,跳过规划")
|
logger.debug(f"[{self.stream_name}] 没有可用动作,跳过规划")
|
||||||
return None
|
self.action_type = "no_action"
|
||||||
|
return no_action
|
||||||
|
|
||||||
# 执行规划
|
# 执行规划
|
||||||
plan_result = await self.planner.plan(message, sender_name)
|
plan_result = await self.planner.plan(message, sender_name)
|
||||||
action_type = plan_result["action_result"]["action_type"]
|
action_type = plan_result["action_result"]["action_type"]
|
||||||
action_data = plan_result["action_result"]["action_data"]
|
action_data = plan_result["action_result"]["action_data"]
|
||||||
reasoning = plan_result["action_result"]["reasoning"]
|
reasoning = plan_result["action_result"]["reasoning"]
|
||||||
|
is_parallel = plan_result["action_result"].get("is_parallel", False)
|
||||||
|
|
||||||
logger.info(f"[{self.stream_name}] Planner决策: {action_type}, 理由: {reasoning}")
|
logger.info(f"[{self.stream_name}] Planner决策: {action_type}, 理由: {reasoning}, 并行执行: {is_parallel}")
|
||||||
self.action_type = action_type # 更新实例属性
|
self.action_type = action_type # 更新实例属性
|
||||||
|
self.is_parallel_action = is_parallel # 新增:保存并行执行标志
|
||||||
|
|
||||||
# 如果规划器决定不执行任何动作
|
# 如果规划器决定不执行任何动作
|
||||||
if action_type == "no_action":
|
if action_type == "no_action":
|
||||||
logger.debug(f"[{self.stream_name}] Planner决定不执行任何额外动作")
|
logger.debug(f"[{self.stream_name}] Planner决定不执行任何额外动作")
|
||||||
return None
|
return no_action
|
||||||
elif action_type == "change_to_focus_chat":
|
elif action_type == "change_to_focus_chat":
|
||||||
logger.info(f"[{self.stream_name}] Planner决定切换到focus聊天模式")
|
logger.info(f"[{self.stream_name}] Planner决定切换到focus聊天模式")
|
||||||
return None
|
return None
|
||||||
@@ -358,14 +355,15 @@ class NormalChat:
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"[{self.stream_name}] 额外动作 {action_type} 执行失败")
|
logger.warning(f"[{self.stream_name}] 额外动作 {action_type} 执行失败")
|
||||||
|
|
||||||
return {"action_type": action_type, "action_data": action_data, "reasoning": reasoning}
|
return {"action_type": action_type, "action_data": action_data, "reasoning": reasoning, "is_parallel": is_parallel}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[{self.stream_name}] Planner执行失败: {e}")
|
logger.error(f"[{self.stream_name}] Planner执行失败: {e}")
|
||||||
return None
|
return no_action
|
||||||
|
|
||||||
# 并行执行回复生成和动作规划
|
# 并行执行回复生成和动作规划
|
||||||
self.action_type = None # 初始化动作类型
|
self.action_type = None # 初始化动作类型
|
||||||
|
self.is_parallel_action = False # 初始化并行动作标志
|
||||||
with Timer("并行生成回复和规划", timing_results):
|
with Timer("并行生成回复和规划", timing_results):
|
||||||
response_set, plan_result = await asyncio.gather(
|
response_set, plan_result = await asyncio.gather(
|
||||||
generate_normal_response(), plan_and_execute_actions(), return_exceptions=True
|
generate_normal_response(), plan_and_execute_actions(), return_exceptions=True
|
||||||
@@ -382,15 +380,15 @@ class NormalChat:
|
|||||||
if isinstance(plan_result, Exception):
|
if isinstance(plan_result, Exception):
|
||||||
logger.error(f"[{self.stream_name}] 动作规划异常: {plan_result}")
|
logger.error(f"[{self.stream_name}] 动作规划异常: {plan_result}")
|
||||||
elif plan_result:
|
elif plan_result:
|
||||||
logger.debug(f"[{self.stream_name}] 额外动作处理完成: {plan_result['action_type']}")
|
logger.debug(f"[{self.stream_name}] 额外动作处理完成: {self.action_type}")
|
||||||
|
|
||||||
if not response_set or (
|
if not response_set or (
|
||||||
self.enable_planner and self.action_type not in ["no_action", "change_to_focus_chat"]
|
self.enable_planner and self.action_type not in ["no_action", "change_to_focus_chat"] and not self.is_parallel_action
|
||||||
):
|
):
|
||||||
if not response_set:
|
if not response_set:
|
||||||
logger.info(f"[{self.stream_name}] 模型未生成回复内容")
|
logger.info(f"[{self.stream_name}] 模型未生成回复内容")
|
||||||
elif self.enable_planner and self.action_type not in ["no_action", "change_to_focus_chat"]:
|
elif self.enable_planner and self.action_type not in ["no_action", "change_to_focus_chat"] and not self.is_parallel_action:
|
||||||
logger.info(f"[{self.stream_name}] 模型选择其他动作")
|
logger.info(f"[{self.stream_name}] 模型选择其他动作(非并行动作)")
|
||||||
# 如果模型未生成回复,移除思考消息
|
# 如果模型未生成回复,移除思考消息
|
||||||
container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id
|
container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id
|
||||||
for msg in container.messages[:]:
|
for msg in container.messages[:]:
|
||||||
@@ -446,7 +444,7 @@ class NormalChat:
|
|||||||
logger.warning(f"[{self.stream_name}] 没有设置切换到focus聊天模式的回调函数,无法执行切换")
|
logger.warning(f"[{self.stream_name}] 没有设置切换到focus聊天模式的回调函数,无法执行切换")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
await self._check_switch_to_focus()
|
# await self._check_switch_to_focus()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
info_catcher.done_catch()
|
info_catcher.done_catch()
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
from typing import List, Any
|
from typing import List, Any, Dict
|
||||||
from src.common.logger_manager import get_logger
|
from src.common.logger_manager import get_logger
|
||||||
from src.chat.focus_chat.planners.action_manager import ActionManager
|
from src.chat.focus_chat.planners.action_manager import ActionManager
|
||||||
|
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType, ChatMode
|
||||||
|
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
|
||||||
|
from src.config.config import global_config
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
logger = get_logger("normal_chat_action_modifier")
|
logger = get_logger("normal_chat_action_modifier")
|
||||||
|
|
||||||
@@ -9,6 +14,7 @@ class NormalChatActionModifier:
|
|||||||
"""Normal Chat动作修改器
|
"""Normal Chat动作修改器
|
||||||
|
|
||||||
负责根据Normal Chat的上下文和状态动态调整可用的动作集合
|
负责根据Normal Chat的上下文和状态动态调整可用的动作集合
|
||||||
|
实现与Focus Chat类似的动作激活策略,但将LLM_JUDGE转换为概率激活以提升性能
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, action_manager: ActionManager, stream_id: str, stream_name: str):
|
def __init__(self, action_manager: ActionManager, stream_id: str, stream_name: str):
|
||||||
@@ -25,9 +31,14 @@ class NormalChatActionModifier:
|
|||||||
self,
|
self,
|
||||||
chat_stream,
|
chat_stream,
|
||||||
recent_replies: List[dict],
|
recent_replies: List[dict],
|
||||||
|
message_content: str,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
):
|
):
|
||||||
"""为Normal Chat修改可用动作集合
|
"""为Normal Chat修改可用动作集合
|
||||||
|
|
||||||
|
实现动作激活策略:
|
||||||
|
1. 基于关联类型的动态过滤
|
||||||
|
2. 基于激活类型的智能判定(LLM_JUDGE转为概率激活)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_stream: 聊天流对象
|
chat_stream: 聊天流对象
|
||||||
@@ -35,24 +46,19 @@ class NormalChatActionModifier:
|
|||||||
**kwargs: 其他参数
|
**kwargs: 其他参数
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 合并所有动作变更
|
|
||||||
merged_action_changes = {"add": [], "remove": []}
|
|
||||||
reasons = []
|
reasons = []
|
||||||
|
merged_action_changes = {"add": [], "remove": []}
|
||||||
|
type_mismatched_actions = [] # 在外层定义避免作用域问题
|
||||||
|
|
||||||
|
self.action_manager.restore_default_actions()
|
||||||
|
|
||||||
# 1. 移除Normal Chat不适用的动作
|
# 第一阶段:基于关联类型的动态过滤
|
||||||
excluded_actions = ["exit_focus_chat_action", "no_reply", "reply"]
|
|
||||||
for action_name in excluded_actions:
|
|
||||||
if action_name in self.action_manager.get_using_actions():
|
|
||||||
merged_action_changes["remove"].append(action_name)
|
|
||||||
reasons.append(f"移除{action_name}(Normal Chat不适用)")
|
|
||||||
|
|
||||||
# 2. 检查动作的关联类型
|
|
||||||
if chat_stream:
|
if chat_stream:
|
||||||
chat_context = chat_stream.context if hasattr(chat_stream, "context") else None
|
chat_context = chat_stream.context if hasattr(chat_stream, "context") else None
|
||||||
if chat_context:
|
if chat_context:
|
||||||
type_mismatched_actions = []
|
# 获取Normal模式下的可用动作(已经过滤了mode_enable)
|
||||||
|
current_using_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL)
|
||||||
current_using_actions = self.action_manager.get_using_actions()
|
# print(f"current_using_actions: {current_using_actions}")
|
||||||
for action_name in current_using_actions.keys():
|
for action_name in current_using_actions.keys():
|
||||||
if action_name in self.all_actions:
|
if action_name in self.all_actions:
|
||||||
data = self.all_actions[action_name]
|
data = self.all_actions[action_name]
|
||||||
@@ -65,26 +71,218 @@ class NormalChatActionModifier:
|
|||||||
merged_action_changes["remove"].extend(type_mismatched_actions)
|
merged_action_changes["remove"].extend(type_mismatched_actions)
|
||||||
reasons.append(f"移除{type_mismatched_actions}(关联类型不匹配)")
|
reasons.append(f"移除{type_mismatched_actions}(关联类型不匹配)")
|
||||||
|
|
||||||
# 应用动作变更
|
# 第二阶段:应用激活类型判定
|
||||||
|
# 构建聊天内容 - 使用与planner一致的方式
|
||||||
|
chat_content = ""
|
||||||
|
if chat_stream and hasattr(chat_stream, 'stream_id'):
|
||||||
|
try:
|
||||||
|
# 获取消息历史,使用与normal_chat_planner相同的方法
|
||||||
|
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
|
||||||
|
chat_id=chat_stream.stream_id,
|
||||||
|
timestamp=time.time(),
|
||||||
|
limit=global_config.focus_chat.observation_context_size, # 使用相同的配置
|
||||||
|
)
|
||||||
|
|
||||||
|
# 构建可读的聊天上下文
|
||||||
|
chat_content = build_readable_messages(
|
||||||
|
message_list_before_now,
|
||||||
|
replace_bot_name=True,
|
||||||
|
merge_messages=False,
|
||||||
|
timestamp_mode="relative",
|
||||||
|
read_mark=0.0,
|
||||||
|
show_actions=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"{self.log_prefix} 成功构建聊天内容,长度: {len(chat_content)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"{self.log_prefix} 构建聊天内容失败: {e}")
|
||||||
|
chat_content = ""
|
||||||
|
|
||||||
|
# 获取当前Normal模式下的动作集进行激活判定
|
||||||
|
current_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL)
|
||||||
|
|
||||||
|
# print(f"current_actions: {current_actions}")
|
||||||
|
# print(f"chat_content: {chat_content}")
|
||||||
|
final_activated_actions = await self._apply_normal_activation_filtering(
|
||||||
|
current_actions,
|
||||||
|
chat_content,
|
||||||
|
message_content
|
||||||
|
)
|
||||||
|
# print(f"final_activated_actions: {final_activated_actions}")
|
||||||
|
|
||||||
|
# 统一处理所有需要移除的动作,避免重复移除
|
||||||
|
all_actions_to_remove = set() # 使用set避免重复
|
||||||
|
|
||||||
|
# 添加关联类型不匹配的动作
|
||||||
|
if type_mismatched_actions:
|
||||||
|
all_actions_to_remove.update(type_mismatched_actions)
|
||||||
|
|
||||||
|
# 添加激活类型判定未通过的动作
|
||||||
|
for action_name in current_actions.keys():
|
||||||
|
if action_name not in final_activated_actions:
|
||||||
|
all_actions_to_remove.add(action_name)
|
||||||
|
|
||||||
|
# 统计移除原因(避免重复)
|
||||||
|
activation_failed_actions = [name for name in current_actions.keys() if name not in final_activated_actions and name not in type_mismatched_actions]
|
||||||
|
if activation_failed_actions:
|
||||||
|
reasons.append(f"移除{activation_failed_actions}(激活类型判定未通过)")
|
||||||
|
|
||||||
|
# 统一执行移除操作
|
||||||
|
for action_name in all_actions_to_remove:
|
||||||
|
success = self.action_manager.remove_action_from_using(action_name)
|
||||||
|
if success:
|
||||||
|
logger.debug(f"{self.log_prefix} 移除动作: {action_name}")
|
||||||
|
else:
|
||||||
|
logger.debug(f"{self.log_prefix} 动作 {action_name} 已经不在使用集中,跳过移除")
|
||||||
|
|
||||||
|
# 应用动作添加(如果有的话)
|
||||||
for action_name in merged_action_changes["add"]:
|
for action_name in merged_action_changes["add"]:
|
||||||
if action_name in self.all_actions and action_name not in excluded_actions:
|
if action_name in self.all_actions:
|
||||||
success = self.action_manager.add_action_to_using(action_name)
|
success = self.action_manager.add_action_to_using(action_name)
|
||||||
if success:
|
if success:
|
||||||
logger.debug(f"{self.log_prefix} 添加动作: {action_name}")
|
logger.debug(f"{self.log_prefix} 添加动作: {action_name}")
|
||||||
|
|
||||||
for action_name in merged_action_changes["remove"]:
|
|
||||||
success = self.action_manager.remove_action_from_using(action_name)
|
|
||||||
if success:
|
|
||||||
logger.debug(f"{self.log_prefix} 移除动作: {action_name}")
|
|
||||||
|
|
||||||
# 记录变更原因
|
# 记录变更原因
|
||||||
if merged_action_changes["add"] or merged_action_changes["remove"]:
|
if reasons:
|
||||||
logger.info(f"{self.log_prefix} 动作调整完成: {' | '.join(reasons)}")
|
logger.info(f"{self.log_prefix} 动作调整完成: {' | '.join(reasons)}")
|
||||||
logger.debug(f"{self.log_prefix} 当前可用动作: {list(self.action_manager.get_using_actions().keys())}")
|
|
||||||
|
# 获取最终的Normal模式可用动作并记录
|
||||||
|
final_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL)
|
||||||
|
logger.debug(f"{self.log_prefix} 当前Normal模式可用动作: {list(final_actions.keys())}")
|
||||||
|
|
||||||
|
async def _apply_normal_activation_filtering(
|
||||||
|
self,
|
||||||
|
actions_with_info: Dict[str, Any],
|
||||||
|
chat_content: str = "",
|
||||||
|
message_content: str = "",
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
应用Normal模式的激活类型过滤逻辑
|
||||||
|
|
||||||
|
与Focus模式的区别:
|
||||||
|
1. LLM_JUDGE类型转换为概率激活(避免LLM调用)
|
||||||
|
2. RANDOM类型保持概率激活
|
||||||
|
3. KEYWORD类型保持关键词匹配
|
||||||
|
4. ALWAYS类型直接激活
|
||||||
|
|
||||||
|
Args:
|
||||||
|
actions_with_info: 带完整信息的动作字典
|
||||||
|
chat_content: 聊天内容
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 过滤后激活的actions字典
|
||||||
|
"""
|
||||||
|
activated_actions = {}
|
||||||
|
|
||||||
|
# 分类处理不同激活类型的actions
|
||||||
|
always_actions = {}
|
||||||
|
random_actions = {}
|
||||||
|
keyword_actions = {}
|
||||||
|
|
||||||
|
for action_name, action_info in actions_with_info.items():
|
||||||
|
# 使用normal_activation_type
|
||||||
|
activation_type = action_info.get("normal_activation_type", ActionActivationType.ALWAYS)
|
||||||
|
|
||||||
|
if activation_type == ActionActivationType.ALWAYS:
|
||||||
|
always_actions[action_name] = action_info
|
||||||
|
elif activation_type == ActionActivationType.RANDOM or activation_type == ActionActivationType.LLM_JUDGE:
|
||||||
|
random_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.info(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,
|
||||||
|
message_content
|
||||||
|
)
|
||||||
|
if should_activate:
|
||||||
|
activated_actions[action_name] = action_info
|
||||||
|
keywords = action_info.get("activation_keywords", [])
|
||||||
|
logger.info(f"{self.log_prefix}激活动作: {action_name},原因: KEYWORD类型匹配关键词({keywords})")
|
||||||
|
else:
|
||||||
|
keywords = action_info.get("activation_keywords", [])
|
||||||
|
logger.info(f"{self.log_prefix}未激活动作: {action_name},原因: KEYWORD类型未匹配关键词({keywords})")
|
||||||
|
# print(f"keywords: {keywords}")
|
||||||
|
# print(f"chat_content: {chat_content}")
|
||||||
|
|
||||||
|
logger.debug(f"{self.log_prefix}Normal模式激活类型过滤完成: {list(activated_actions.keys())}")
|
||||||
|
return activated_actions
|
||||||
|
|
||||||
|
def _check_keyword_activation(
|
||||||
|
self,
|
||||||
|
action_name: str,
|
||||||
|
action_info: Dict[str, Any],
|
||||||
|
chat_content: str = "",
|
||||||
|
message_content: str = "",
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
检查是否匹配关键词触发条件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action_name: 动作名称
|
||||||
|
action_info: 动作信息
|
||||||
|
chat_content: 聊天内容(已经是格式化后的可读消息)
|
||||||
|
|
||||||
|
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 = chat_content +message_content
|
||||||
|
|
||||||
|
# 如果不区分大小写,转换为小写
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
# print(f"search_text: {search_text}")
|
||||||
|
# print(f"activation_keywords: {activation_keywords}")
|
||||||
|
|
||||||
|
if matched_keywords:
|
||||||
|
logger.info(f"{self.log_prefix}动作 {action_name} 匹配到关键词: {matched_keywords}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.info(f"{self.log_prefix}动作 {action_name} 未匹配到任何关键词: {activation_keywords}")
|
||||||
|
return False
|
||||||
|
|
||||||
def get_available_actions_count(self) -> int:
|
def get_available_actions_count(self) -> int:
|
||||||
"""获取当前可用动作数量(排除默认的no_action)"""
|
"""获取当前可用动作数量(排除默认的no_action)"""
|
||||||
current_actions = self.action_manager.get_using_actions()
|
current_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL)
|
||||||
# 排除no_action(如果存在)
|
# 排除no_action(如果存在)
|
||||||
filtered_actions = {k: v for k, v in current_actions.items() if k != "no_action"}
|
filtered_actions = {k: v for k, v in current_actions.items() if k != "no_action"}
|
||||||
return len(filtered_actions)
|
return len(filtered_actions)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from src.common.logger_manager import get_logger
|
|||||||
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
||||||
from src.individuality.individuality import individuality
|
from src.individuality.individuality import individuality
|
||||||
from src.chat.focus_chat.planners.action_manager import ActionManager
|
from src.chat.focus_chat.planners.action_manager import ActionManager
|
||||||
|
from src.chat.focus_chat.planners.actions.base_action import ChatMode
|
||||||
from src.chat.message_receive.message import MessageThinking
|
from src.chat.message_receive.message import MessageThinking
|
||||||
from json_repair import repair_json
|
from json_repair import repair_json
|
||||||
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
|
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
|
||||||
@@ -98,16 +99,18 @@ class NormalChatPlanner:
|
|||||||
|
|
||||||
self_info = name_block + personality_block + identity_block
|
self_info = name_block + personality_block + identity_block
|
||||||
|
|
||||||
# 获取当前可用的动作
|
# 获取当前可用的动作,使用Normal模式过滤
|
||||||
current_available_actions = self.action_manager.get_using_actions()
|
current_available_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL)
|
||||||
|
|
||||||
|
# 注意:动作的激活判定现在在 normal_chat_action_modifier 中完成
|
||||||
|
# 这里直接使用经过 action_modifier 处理后的最终动作集
|
||||||
|
# 符合职责分离原则:ActionModifier负责动作管理,Planner专注于决策
|
||||||
|
|
||||||
# 如果没有可用动作或只有no_action动作,直接返回no_action
|
# 如果没有可用动作,直接返回no_action
|
||||||
if not current_available_actions or (
|
if not current_available_actions:
|
||||||
len(current_available_actions) == 1 and "no_action" in current_available_actions
|
logger.debug(f"{self.log_prefix}规划器: 没有可用动作,返回no_action")
|
||||||
):
|
|
||||||
logger.debug(f"{self.log_prefix}规划器: 没有可用动作或只有no_action动作,返回no_action")
|
|
||||||
return {
|
return {
|
||||||
"action_result": {"action_type": action, "action_data": action_data, "reasoning": reasoning},
|
"action_result": {"action_type": action, "action_data": action_data, "reasoning": reasoning, "is_parallel": True},
|
||||||
"chat_context": "",
|
"chat_context": "",
|
||||||
"action_prompt": "",
|
"action_prompt": "",
|
||||||
}
|
}
|
||||||
@@ -138,7 +141,7 @@ class NormalChatPlanner:
|
|||||||
if not prompt:
|
if not prompt:
|
||||||
logger.warning(f"{self.log_prefix}规划器: 构建提示词失败")
|
logger.warning(f"{self.log_prefix}规划器: 构建提示词失败")
|
||||||
return {
|
return {
|
||||||
"action_result": {"action_type": action, "action_data": action_data, "reasoning": reasoning},
|
"action_result": {"action_type": action, "action_data": action_data, "reasoning": reasoning, "is_parallel": False},
|
||||||
"chat_context": chat_context,
|
"chat_context": chat_context,
|
||||||
"action_prompt": "",
|
"action_prompt": "",
|
||||||
}
|
}
|
||||||
@@ -185,13 +188,21 @@ class NormalChatPlanner:
|
|||||||
|
|
||||||
except Exception as outer_e:
|
except Exception as outer_e:
|
||||||
logger.error(f"{self.log_prefix}规划器异常: {outer_e}")
|
logger.error(f"{self.log_prefix}规划器异常: {outer_e}")
|
||||||
chat_context = "无法获取聊天上下文" # 设置默认值
|
# 设置异常时的默认值
|
||||||
prompt = "" # 设置默认值
|
current_available_actions = {}
|
||||||
|
chat_context = "无法获取聊天上下文"
|
||||||
|
prompt = ""
|
||||||
action = "no_action"
|
action = "no_action"
|
||||||
reasoning = "规划器出现异常,使用默认动作"
|
reasoning = "规划器出现异常,使用默认动作"
|
||||||
action_data = {}
|
action_data = {}
|
||||||
|
|
||||||
logger.debug(f"{self.log_prefix}规划器决策动作:{action}, 动作信息: '{action_data}', 理由: {reasoning}")
|
# 检查动作是否支持并行执行
|
||||||
|
is_parallel = False
|
||||||
|
if action in current_available_actions:
|
||||||
|
action_info = current_available_actions[action]
|
||||||
|
is_parallel = action_info.get("parallel_action", False)
|
||||||
|
|
||||||
|
logger.debug(f"{self.log_prefix}规划器决策动作:{action}, 动作信息: '{action_data}', 理由: {reasoning}, 并行执行: {is_parallel}")
|
||||||
|
|
||||||
# 恢复到默认动作集
|
# 恢复到默认动作集
|
||||||
self.action_manager.restore_actions()
|
self.action_manager.restore_actions()
|
||||||
@@ -212,6 +223,7 @@ class NormalChatPlanner:
|
|||||||
"action_type": action,
|
"action_type": action,
|
||||||
"action_data": action_data,
|
"action_data": action_data,
|
||||||
"reasoning": reasoning,
|
"reasoning": reasoning,
|
||||||
|
"is_parallel": is_parallel,
|
||||||
"action_record": json.dumps(action_record, ensure_ascii=False)
|
"action_record": json.dumps(action_record, ensure_ascii=False)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,4 +316,6 @@ class NormalChatPlanner:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
init_prompt()
|
init_prompt()
|
||||||
|
|||||||
@@ -531,7 +531,6 @@ class PersonInfoManager:
|
|||||||
"know_since": int(datetime.datetime.now().timestamp()),
|
"know_since": int(datetime.datetime.now().timestamp()),
|
||||||
"last_know": int(datetime.datetime.now().timestamp()),
|
"last_know": int(datetime.datetime.now().timestamp()),
|
||||||
"impression": None,
|
"impression": None,
|
||||||
"interaction": None,
|
|
||||||
"points": [],
|
"points": [],
|
||||||
"forgotten_points": []
|
"forgotten_points": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ class RelationshipManager:
|
|||||||
if not person_name or person_name == "none":
|
if not person_name or person_name == "none":
|
||||||
return ""
|
return ""
|
||||||
impression = await person_info_manager.get_value(person_id, "impression")
|
impression = await person_info_manager.get_value(person_id, "impression")
|
||||||
interaction = await person_info_manager.get_value(person_id, "interaction")
|
|
||||||
points = await person_info_manager.get_value(person_id, "points") or []
|
points = await person_info_manager.get_value(person_id, "points") or []
|
||||||
|
|
||||||
if isinstance(points, str):
|
if isinstance(points, str):
|
||||||
@@ -141,11 +140,9 @@ class RelationshipManager:
|
|||||||
relation_prompt = f"'{person_name}' ,ta在{platform}上的昵称是{nickname_str}。"
|
relation_prompt = f"'{person_name}' ,ta在{platform}上的昵称是{nickname_str}。"
|
||||||
|
|
||||||
|
|
||||||
if impression:
|
# if impression:
|
||||||
relation_prompt += f"你对ta的印象是:{impression}。"
|
# relation_prompt += f"你对ta的印象是:{impression}。"
|
||||||
|
|
||||||
if interaction:
|
|
||||||
relation_prompt += f"你与ta的关系是:{interaction}。"
|
|
||||||
|
|
||||||
if random_points:
|
if random_points:
|
||||||
for point in random_points:
|
for point in random_points:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import base64 # 新增:用于Base64编码
|
|||||||
import traceback # 新增:用于打印堆栈跟踪
|
import traceback # 新增:用于打印堆栈跟踪
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
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
|
||||||
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType
|
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType, ChatMode
|
||||||
from src.common.logger_manager import get_logger
|
from src.common.logger_manager import get_logger
|
||||||
from .generate_pic_config import generate_config
|
from .generate_pic_config import generate_config
|
||||||
|
|
||||||
@@ -35,11 +35,18 @@ class PicAction(PluginAction):
|
|||||||
"当有人要求你生成并发送一张图片时使用",
|
"当有人要求你生成并发送一张图片时使用",
|
||||||
"当有人让你画一张图时使用",
|
"当有人让你画一张图时使用",
|
||||||
]
|
]
|
||||||
default = True
|
enable_plugin = True
|
||||||
action_config_file_name = "pic_action_config.toml"
|
action_config_file_name = "pic_action_config.toml"
|
||||||
|
|
||||||
# 激活类型设置 - 使用LLM判定,能更好理解用户意图
|
# 激活类型设置
|
||||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
focus_activation_type = ActionActivationType.LLM_JUDGE # Focus模式使用LLM判定,精确理解需求
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD # Normal模式使用关键词激活,快速响应
|
||||||
|
|
||||||
|
# 关键词设置(用于Normal模式)
|
||||||
|
activation_keywords = ["画", "绘制", "生成图片", "画图", "draw", "paint", "图片生成"]
|
||||||
|
keyword_case_sensitive = False
|
||||||
|
|
||||||
|
# LLM判定提示词(用于Focus模式)
|
||||||
llm_judge_prompt = """
|
llm_judge_prompt = """
|
||||||
判定是否需要使用图片生成动作的条件:
|
判定是否需要使用图片生成动作的条件:
|
||||||
1. 用户明确要求画图、生成图片或创作图像
|
1. 用户明确要求画图、生成图片或创作图像
|
||||||
@@ -60,11 +67,20 @@ class PicAction(PluginAction):
|
|||||||
4. 技术讨论中提到绘图概念但无生成需求
|
4. 技术讨论中提到绘图概念但无生成需求
|
||||||
5. 用户明确表示不需要图片时
|
5. 用户明确表示不需要图片时
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Random激活概率(备用)
|
||||||
|
random_activation_probability = 0.15 # 适中概率,图片生成比较有趣
|
||||||
|
|
||||||
# 简单的请求缓存,避免短时间内重复请求
|
# 简单的请求缓存,避免短时间内重复请求
|
||||||
_request_cache = {}
|
_request_cache = {}
|
||||||
_cache_max_size = 10
|
_cache_max_size = 10
|
||||||
|
|
||||||
|
# 模式启用设置 - 图片生成在所有模式下可用
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
|
||||||
|
# 并行执行设置 - 图片生成可以与回复并行执行,不覆盖回复内容
|
||||||
|
parallel_action = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_cache_key(cls, description: str, model: str, size: str) -> str:
|
def _get_cache_key(cls, description: str, model: str, size: str) -> str:
|
||||||
"""生成缓存键"""
|
"""生成缓存键"""
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from src.common.logger_manager import get_logger
|
from src.common.logger_manager import get_logger
|
||||||
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
from src.chat.focus_chat.planners.actions.plugin_action import PluginAction, register_action, ActionActivationType
|
||||||
|
from src.chat.focus_chat.planners.actions.base_action import ChatMode
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
logger = get_logger("mute_action")
|
logger = get_logger("mute_action")
|
||||||
@@ -22,12 +23,20 @@ class MuteAction(PluginAction):
|
|||||||
"当有人发了擦边,或者色情内容时使用",
|
"当有人发了擦边,或者色情内容时使用",
|
||||||
"当有人要求禁言自己时使用",
|
"当有人要求禁言自己时使用",
|
||||||
]
|
]
|
||||||
default = True # 默认动作,是否手动添加到使用集
|
enable_plugin = True # 启用插件
|
||||||
associated_types = ["command", "text"]
|
associated_types = ["command", "text"]
|
||||||
action_config_file_name = "mute_action_config.toml"
|
action_config_file_name = "mute_action_config.toml"
|
||||||
|
|
||||||
# 激活类型设置 - 使用LLM判定,因为禁言是严肃的管理动作,需要谨慎判断
|
# 激活类型设置
|
||||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
focus_activation_type = ActionActivationType.LLM_JUDGE # Focus模式使用LLM判定,确保谨慎
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD # Normal模式使用关键词激活,快速响应
|
||||||
|
|
||||||
|
|
||||||
|
# 关键词设置(用于Normal模式)
|
||||||
|
activation_keywords = ["禁言", "mute", "ban", "silence"]
|
||||||
|
keyword_case_sensitive = False
|
||||||
|
|
||||||
|
# LLM判定提示词(用于Focus模式)
|
||||||
llm_judge_prompt = """
|
llm_judge_prompt = """
|
||||||
判定是否需要使用禁言动作的严格条件:
|
判定是否需要使用禁言动作的严格条件:
|
||||||
|
|
||||||
@@ -49,6 +58,15 @@ class MuteAction(PluginAction):
|
|||||||
注意:禁言是严厉措施,只在明确违规或用户主动要求时使用。
|
注意:禁言是严厉措施,只在明确违规或用户主动要求时使用。
|
||||||
宁可保守也不要误判,保护用户的发言权利。
|
宁可保守也不要误判,保护用户的发言权利。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Random激活概率(备用)
|
||||||
|
random_activation_probability = 0.05 # 设置很低的概率作为兜底
|
||||||
|
|
||||||
|
# 模式启用设置 - 禁言功能在所有模式下都可用
|
||||||
|
mode_enable = ChatMode.ALL
|
||||||
|
|
||||||
|
# 并行执行设置 - 禁言动作可以与回复并行执行,不覆盖回复内容
|
||||||
|
parallel_action = True
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from src.common.logger_manager import get_logger
|
from src.common.logger_manager import get_logger
|
||||||
|
from src.chat.focus_chat.planners.actions.base_action import ActionActivationType
|
||||||
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
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
@@ -20,8 +21,18 @@ class TTSAction(PluginAction):
|
|||||||
"当表达内容更适合用语音而不是文字传达时使用",
|
"当表达内容更适合用语音而不是文字传达时使用",
|
||||||
"当用户想听到语音回答而非阅读文本时使用",
|
"当用户想听到语音回答而非阅读文本时使用",
|
||||||
]
|
]
|
||||||
default = True # 设为默认动作
|
enable_plugin = True # 启用插件
|
||||||
associated_types = ["tts_text"]
|
associated_types = ["tts_text"]
|
||||||
|
|
||||||
|
focus_activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
normal_activation_type = ActionActivationType.KEYWORD
|
||||||
|
|
||||||
|
# 关键词配置 - Normal模式下使用关键词触发
|
||||||
|
activation_keywords = ["语音", "tts", "播报", "读出来", "语音播放", "听", "朗读"]
|
||||||
|
keyword_case_sensitive = False
|
||||||
|
|
||||||
|
# 并行执行设置 - TTS可以与回复并行执行,不覆盖回复内容
|
||||||
|
parallel_action = False
|
||||||
|
|
||||||
async def process(self) -> Tuple[bool, str]:
|
async def process(self) -> Tuple[bool, str]:
|
||||||
"""处理TTS文本转语音动作"""
|
"""处理TTS文本转语音动作"""
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ class VTBAction(PluginAction):
|
|||||||
"当回应内容需要更生动的情感表达时使用",
|
"当回应内容需要更生动的情感表达时使用",
|
||||||
"当想要通过预设动作增强互动体验时使用",
|
"当想要通过预设动作增强互动体验时使用",
|
||||||
]
|
]
|
||||||
default = True # 设为默认动作
|
enable_plugin = True # 启用插件
|
||||||
associated_types = ["vtb_text"]
|
associated_types = ["vtb_text"]
|
||||||
|
|
||||||
# 激活类型设置 - 使用LLM判定,因为需要根据情感表达需求判断
|
# 激活类型设置
|
||||||
action_activation_type = ActionActivationType.LLM_JUDGE
|
focus_activation_type = ActionActivationType.LLM_JUDGE # Focus模式使用LLM判定,精确识别情感表达需求
|
||||||
|
normal_activation_type = ActionActivationType.RANDOM # Normal模式使用随机激活,增加趣味性
|
||||||
|
|
||||||
|
# LLM判定提示词(用于Focus模式)
|
||||||
llm_judge_prompt = """
|
llm_judge_prompt = """
|
||||||
判定是否需要使用VTB虚拟主播动作的条件:
|
判定是否需要使用VTB虚拟主播动作的条件:
|
||||||
1. 当前聊天内容涉及明显的情感表达需求
|
1. 当前聊天内容涉及明显的情感表达需求
|
||||||
@@ -38,6 +41,9 @@ class VTBAction(PluginAction):
|
|||||||
3. 不涉及情感的日常对话
|
3. 不涉及情感的日常对话
|
||||||
4. 已经有足够的情感表达
|
4. 已经有足够的情感表达
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Random激活概率(用于Normal模式)
|
||||||
|
random_activation_probability = 0.08 # 较低概率,避免过度使用
|
||||||
|
|
||||||
async def process(self) -> Tuple[bool, str]:
|
async def process(self) -> Tuple[bool, str]:
|
||||||
"""处理VTB虚拟主播动作"""
|
"""处理VTB虚拟主播动作"""
|
||||||
|
|||||||
@@ -1,608 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
import asyncio
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from typing import List, Dict, Any, Tuple, Optional
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# 添加项目根目录到Python路径
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
project_root = os.path.dirname(current_dir)
|
|
||||||
sys.path.append(project_root)
|
|
||||||
|
|
||||||
from src.common.message_repository import find_messages
|
|
||||||
from src.common.database.database_model import ActionRecords, ChatStreams
|
|
||||||
from src.config.config import global_config
|
|
||||||
from src.person_info.person_info import person_info_manager
|
|
||||||
from src.chat.utils.utils import translate_timestamp_to_human_readable
|
|
||||||
from src.chat.heart_flow.observation.observation import Observation
|
|
||||||
from src.llm_models.utils_model import LLMRequest
|
|
||||||
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
|
||||||
from src.person_info.relationship_manager import relationship_manager
|
|
||||||
from src.common.logger_manager import get_logger
|
|
||||||
from src.chat.focus_chat.info.info_base import InfoBase
|
|
||||||
from src.chat.focus_chat.info.relation_info import RelationInfo
|
|
||||||
|
|
||||||
logger = get_logger("processor")
|
|
||||||
|
|
||||||
async def get_person_id_list(messages: List[Dict[str, Any]]) -> List[str]:
|
|
||||||
"""
|
|
||||||
从消息列表中提取不重复的 person_id 列表 (忽略机器人自身)。
|
|
||||||
|
|
||||||
Args:
|
|
||||||
messages: 消息字典列表。
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
一个包含唯一 person_id 的列表。
|
|
||||||
"""
|
|
||||||
person_ids_set = set() # 使用集合来自动去重
|
|
||||||
|
|
||||||
for msg in messages:
|
|
||||||
platform = msg.get("user_platform")
|
|
||||||
user_id = msg.get("user_id")
|
|
||||||
|
|
||||||
# 检查必要信息是否存在 且 不是机器人自己
|
|
||||||
if not all([platform, user_id]) or user_id == global_config.bot.qq_account:
|
|
||||||
continue
|
|
||||||
|
|
||||||
person_id = person_info_manager.get_person_id(platform, user_id)
|
|
||||||
|
|
||||||
# 只有当获取到有效 person_id 时才添加
|
|
||||||
if person_id:
|
|
||||||
person_ids_set.add(person_id)
|
|
||||||
|
|
||||||
return list(person_ids_set) # 将集合转换为列表返回
|
|
||||||
|
|
||||||
class ChattingObservation(Observation):
|
|
||||||
def __init__(self, chat_id):
|
|
||||||
super().__init__(chat_id)
|
|
||||||
self.chat_id = chat_id
|
|
||||||
self.platform = "qq"
|
|
||||||
|
|
||||||
# 从数据库获取聊天类型和目标信息
|
|
||||||
chat_info = ChatStreams.select().where(ChatStreams.stream_id == chat_id).first()
|
|
||||||
self.is_group_chat = True
|
|
||||||
self.chat_target_info = {
|
|
||||||
"person_name": chat_info.group_name if chat_info else None,
|
|
||||||
"user_nickname": chat_info.group_name if chat_info else None
|
|
||||||
}
|
|
||||||
|
|
||||||
# 初始化其他属性
|
|
||||||
self.talking_message = []
|
|
||||||
self.talking_message_str = ""
|
|
||||||
self.talking_message_str_truncate = ""
|
|
||||||
self.name = global_config.bot.nickname
|
|
||||||
self.nick_name = global_config.bot.alias_names
|
|
||||||
self.max_now_obs_len = global_config.focus_chat.observation_context_size
|
|
||||||
self.overlap_len = global_config.focus_chat.compressed_length
|
|
||||||
self.mid_memories = []
|
|
||||||
self.max_mid_memory_len = global_config.focus_chat.compress_length_limit
|
|
||||||
self.mid_memory_info = ""
|
|
||||||
self.person_list = []
|
|
||||||
self.oldest_messages = []
|
|
||||||
self.oldest_messages_str = ""
|
|
||||||
self.compressor_prompt = ""
|
|
||||||
self.last_observe_time = 0
|
|
||||||
|
|
||||||
def get_observe_info(self, ids=None):
|
|
||||||
"""获取观察信息"""
|
|
||||||
return self.talking_message_str
|
|
||||||
|
|
||||||
def init_prompt():
|
|
||||||
relationship_prompt = """
|
|
||||||
<聊天记录>
|
|
||||||
{chat_observe_info}
|
|
||||||
</聊天记录>
|
|
||||||
|
|
||||||
<人物信息>
|
|
||||||
{relation_prompt}
|
|
||||||
</人物信息>
|
|
||||||
|
|
||||||
请区分聊天记录的内容和你之前对人的了解,聊天记录是现在发生的事情,人物信息是之前对某个人的持久的了解。
|
|
||||||
|
|
||||||
{name_block}
|
|
||||||
现在请你总结提取某人的信息,提取成一串文本
|
|
||||||
1. 根据聊天记录的需求,如果需要你和某个人的信息,请输出你和这个人之间精简的信息
|
|
||||||
2. 如果没有特别需要提及的信息,就不用输出这个人的信息
|
|
||||||
3. 如果有人问你对他的看法或者关系,请输出你和这个人之间的信息
|
|
||||||
|
|
||||||
请从这些信息中提取出你对某人的了解信息,信息提取成一串文本:
|
|
||||||
|
|
||||||
请严格按照以下输出格式,不要输出多余内容,person_name可以有多个:
|
|
||||||
{{
|
|
||||||
"person_name": "信息",
|
|
||||||
"person_name2": "信息",
|
|
||||||
"person_name3": "信息",
|
|
||||||
}}
|
|
||||||
|
|
||||||
"""
|
|
||||||
Prompt(relationship_prompt, "relationship_prompt")
|
|
||||||
|
|
||||||
class RelationshipProcessor:
|
|
||||||
log_prefix = "关系"
|
|
||||||
|
|
||||||
def __init__(self, subheartflow_id: str):
|
|
||||||
self.subheartflow_id = subheartflow_id
|
|
||||||
|
|
||||||
self.llm_model = LLMRequest(
|
|
||||||
model=global_config.model.relation,
|
|
||||||
request_type="relation",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 直接从数据库获取名称
|
|
||||||
chat_info = ChatStreams.select().where(ChatStreams.stream_id == subheartflow_id).first()
|
|
||||||
name = chat_info.group_name if chat_info else "未知"
|
|
||||||
self.log_prefix = f"[{name}] "
|
|
||||||
|
|
||||||
async def process_info(
|
|
||||||
self, observations: Optional[List[Observation]] = None, running_memorys: Optional[List[Dict]] = None, *infos
|
|
||||||
) -> List[InfoBase]:
|
|
||||||
"""处理信息对象
|
|
||||||
|
|
||||||
Args:
|
|
||||||
*infos: 可变数量的InfoBase类型的信息对象
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[InfoBase]: 处理后的结构化信息列表
|
|
||||||
"""
|
|
||||||
relation_info_str = await self.relation_identify(observations)
|
|
||||||
|
|
||||||
if relation_info_str:
|
|
||||||
relation_info = RelationInfo()
|
|
||||||
relation_info.set_relation_info(relation_info_str)
|
|
||||||
else:
|
|
||||||
relation_info = None
|
|
||||||
return None
|
|
||||||
|
|
||||||
return [relation_info]
|
|
||||||
|
|
||||||
async def relation_identify(
|
|
||||||
self, observations: Optional[List[Observation]] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
在回复前进行思考,生成内心想法并收集工具调用结果
|
|
||||||
|
|
||||||
参数:
|
|
||||||
observations: 观察信息
|
|
||||||
|
|
||||||
返回:
|
|
||||||
如果return_prompt为False:
|
|
||||||
tuple: (current_mind, past_mind) 当前想法和过去的想法列表
|
|
||||||
如果return_prompt为True:
|
|
||||||
tuple: (current_mind, past_mind, prompt) 当前想法、过去的想法列表和使用的prompt
|
|
||||||
"""
|
|
||||||
|
|
||||||
if observations is None:
|
|
||||||
observations = []
|
|
||||||
for observation in observations:
|
|
||||||
if isinstance(observation, ChattingObservation):
|
|
||||||
# 获取聊天元信息
|
|
||||||
is_group_chat = observation.is_group_chat
|
|
||||||
chat_target_info = observation.chat_target_info
|
|
||||||
chat_target_name = "对方" # 私聊默认名称
|
|
||||||
if not is_group_chat and chat_target_info:
|
|
||||||
# 优先使用person_name,其次user_nickname,最后回退到默认值
|
|
||||||
chat_target_name = (
|
|
||||||
chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or chat_target_name
|
|
||||||
)
|
|
||||||
# 获取聊天内容
|
|
||||||
chat_observe_info = observation.get_observe_info()
|
|
||||||
person_list = observation.person_list
|
|
||||||
|
|
||||||
nickname_str = ""
|
|
||||||
for nicknames in global_config.bot.alias_names:
|
|
||||||
nickname_str += f"{nicknames},"
|
|
||||||
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
|
|
||||||
|
|
||||||
if is_group_chat:
|
|
||||||
relation_prompt_init = "你对群聊里的人的印象是:\n"
|
|
||||||
else:
|
|
||||||
relation_prompt_init = "你对对方的印象是:\n"
|
|
||||||
|
|
||||||
relation_prompt = ""
|
|
||||||
for person in person_list:
|
|
||||||
relation_prompt += f"{await relationship_manager.build_relationship_info(person, is_id=True)}\n"
|
|
||||||
|
|
||||||
if relation_prompt:
|
|
||||||
relation_prompt = relation_prompt_init + relation_prompt
|
|
||||||
else:
|
|
||||||
relation_prompt = relation_prompt_init + "没有特别在意的人\n"
|
|
||||||
|
|
||||||
prompt = (await global_prompt_manager.get_prompt_async("relationship_prompt")).format(
|
|
||||||
name_block=name_block,
|
|
||||||
relation_prompt=relation_prompt,
|
|
||||||
time_now=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
|
||||||
chat_observe_info=chat_observe_info,
|
|
||||||
)
|
|
||||||
# The above code is a Python script that is attempting to print the variable `prompt`.
|
|
||||||
# However, the code is not complete as the content of the `prompt` variable is missing.
|
|
||||||
# print(prompt)
|
|
||||||
|
|
||||||
content = ""
|
|
||||||
try:
|
|
||||||
content, _ = await self.llm_model.generate_response_async(prompt=prompt)
|
|
||||||
if not content:
|
|
||||||
logger.warning(f"{self.log_prefix} LLM返回空结果,关系识别失败。")
|
|
||||||
except Exception as e:
|
|
||||||
# 处理总体异常
|
|
||||||
logger.error(f"{self.log_prefix} 执行LLM请求或处理响应时出错: {e}")
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
content = "关系识别过程中出现错误"
|
|
||||||
|
|
||||||
if content == "None":
|
|
||||||
content = ""
|
|
||||||
# 记录初步思考结果
|
|
||||||
logger.info(f"{self.log_prefix} 关系识别prompt: \n{prompt}\n")
|
|
||||||
logger.info(f"{self.log_prefix} 关系识别: {content}")
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
init_prompt()
|
|
||||||
|
|
||||||
# ==== 只复制最小依赖的relationship_manager ====
|
|
||||||
class SimpleRelationshipManager:
|
|
||||||
async def build_relationship_info(self, person, is_id: bool = False) -> str:
|
|
||||||
if is_id:
|
|
||||||
person_id = person
|
|
||||||
else:
|
|
||||||
person_id = person_info_manager.get_person_id(person[0], person[1])
|
|
||||||
|
|
||||||
person_name = await person_info_manager.get_value(person_id, "person_name")
|
|
||||||
if not person_name or person_name == "none":
|
|
||||||
return ""
|
|
||||||
impression = await person_info_manager.get_value(person_id, "impression")
|
|
||||||
interaction = await person_info_manager.get_value(person_id, "interaction")
|
|
||||||
points = await person_info_manager.get_value(person_id, "points") or []
|
|
||||||
|
|
||||||
if isinstance(points, str):
|
|
||||||
try:
|
|
||||||
import ast
|
|
||||||
points = ast.literal_eval(points)
|
|
||||||
except (SyntaxError, ValueError):
|
|
||||||
points = []
|
|
||||||
|
|
||||||
import random
|
|
||||||
random_points = random.sample(points, min(3, len(points))) if points else []
|
|
||||||
|
|
||||||
nickname_str = await person_info_manager.get_value(person_id, "nickname")
|
|
||||||
platform = await person_info_manager.get_value(person_id, "platform")
|
|
||||||
relation_prompt = f"'{person_name}' ,ta在{platform}上的昵称是{nickname_str}。"
|
|
||||||
|
|
||||||
if impression:
|
|
||||||
relation_prompt += f"你对ta的印象是:{impression}。"
|
|
||||||
if interaction:
|
|
||||||
relation_prompt += f"你与ta的关系是:{interaction}。"
|
|
||||||
if random_points:
|
|
||||||
for point in random_points:
|
|
||||||
point_str = f"时间:{point[2]}。内容:{point[0]}"
|
|
||||||
relation_prompt += f"你记得{person_name}最近的点是:{point_str}。"
|
|
||||||
return relation_prompt
|
|
||||||
|
|
||||||
# 用于替换原有的relationship_manager
|
|
||||||
relationship_manager = SimpleRelationshipManager()
|
|
||||||
|
|
||||||
def get_raw_msg_by_timestamp_random(
|
|
||||||
timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest"
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""先在范围时间戳内随机选择一条消息,取得消息的chat_id,然后根据chat_id获取该聊天在指定时间戳范围内的消息"""
|
|
||||||
# 获取所有消息,只取chat_id字段
|
|
||||||
filter_query = {"time": {"$gt": timestamp_start, "$lt": timestamp_end}}
|
|
||||||
all_msgs = find_messages(message_filter=filter_query)
|
|
||||||
if not all_msgs:
|
|
||||||
return []
|
|
||||||
# 随机选一条
|
|
||||||
msg = random.choice(all_msgs)
|
|
||||||
chat_id = msg["chat_id"]
|
|
||||||
timestamp_start = msg["time"]
|
|
||||||
# 用 chat_id 获取该聊天在指定时间戳范围内的消息
|
|
||||||
filter_query = {"chat_id": chat_id, "time": {"$gt": timestamp_start, "$lt": timestamp_end}}
|
|
||||||
sort_order = [("time", 1)] if limit == 0 else None
|
|
||||||
return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode="earliest")
|
|
||||||
|
|
||||||
def _build_readable_messages_internal(
|
|
||||||
messages: List[Dict[str, Any]],
|
|
||||||
replace_bot_name: bool = True,
|
|
||||||
merge_messages: bool = False,
|
|
||||||
timestamp_mode: str = "relative",
|
|
||||||
truncate: bool = False,
|
|
||||||
) -> Tuple[str, List[Tuple[float, str, str]]]:
|
|
||||||
"""内部辅助函数,构建可读消息字符串和原始消息详情列表"""
|
|
||||||
if not messages:
|
|
||||||
return "", []
|
|
||||||
|
|
||||||
message_details_raw: List[Tuple[float, str, str]] = []
|
|
||||||
|
|
||||||
# 1 & 2: 获取发送者信息并提取消息组件
|
|
||||||
for msg in messages:
|
|
||||||
# 检查是否是动作记录
|
|
||||||
if msg.get("is_action_record", False):
|
|
||||||
is_action = True
|
|
||||||
timestamp = msg.get("time")
|
|
||||||
content = msg.get("display_message", "")
|
|
||||||
message_details_raw.append((timestamp, global_config.bot.nickname, content, is_action))
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 检查并修复缺少的user_info字段
|
|
||||||
if "user_info" not in msg:
|
|
||||||
msg["user_info"] = {
|
|
||||||
"platform": msg.get("user_platform", ""),
|
|
||||||
"user_id": msg.get("user_id", ""),
|
|
||||||
"user_nickname": msg.get("user_nickname", ""),
|
|
||||||
"user_cardname": msg.get("user_cardname", ""),
|
|
||||||
}
|
|
||||||
|
|
||||||
user_info = msg.get("user_info", {})
|
|
||||||
platform = user_info.get("platform")
|
|
||||||
user_id = user_info.get("user_id")
|
|
||||||
user_nickname = user_info.get("user_nickname")
|
|
||||||
user_cardname = user_info.get("user_cardname")
|
|
||||||
timestamp = msg.get("time")
|
|
||||||
|
|
||||||
if msg.get("display_message"):
|
|
||||||
content = msg.get("display_message")
|
|
||||||
else:
|
|
||||||
content = msg.get("processed_plain_text", "")
|
|
||||||
|
|
||||||
if "ᶠ" in content:
|
|
||||||
content = content.replace("ᶠ", "")
|
|
||||||
if "ⁿ" in content:
|
|
||||||
content = content.replace("ⁿ", "")
|
|
||||||
|
|
||||||
if not all([platform, user_id, timestamp is not None]):
|
|
||||||
continue
|
|
||||||
|
|
||||||
person_id = person_info_manager.get_person_id(platform, user_id)
|
|
||||||
if replace_bot_name and user_id == global_config.bot.qq_account:
|
|
||||||
person_name = f"{global_config.bot.nickname}(你)"
|
|
||||||
else:
|
|
||||||
person_name = person_info_manager.get_value_sync(person_id, "person_name")
|
|
||||||
|
|
||||||
if not person_name:
|
|
||||||
if user_cardname:
|
|
||||||
person_name = f"昵称:{user_cardname}"
|
|
||||||
elif user_nickname:
|
|
||||||
person_name = f"{user_nickname}"
|
|
||||||
else:
|
|
||||||
person_name = "某人"
|
|
||||||
|
|
||||||
if content != "":
|
|
||||||
message_details_raw.append((timestamp, person_name, content, False))
|
|
||||||
|
|
||||||
if not message_details_raw:
|
|
||||||
return "", []
|
|
||||||
|
|
||||||
message_details_raw.sort(key=lambda x: x[0])
|
|
||||||
|
|
||||||
# 为每条消息添加一个标记,指示它是否是动作记录
|
|
||||||
message_details_with_flags = []
|
|
||||||
for timestamp, name, content, is_action in message_details_raw:
|
|
||||||
message_details_with_flags.append((timestamp, name, content, is_action))
|
|
||||||
|
|
||||||
# 应用截断逻辑
|
|
||||||
message_details: List[Tuple[float, str, str, bool]] = []
|
|
||||||
n_messages = len(message_details_with_flags)
|
|
||||||
if truncate and n_messages > 0:
|
|
||||||
for i, (timestamp, name, content, is_action) in enumerate(message_details_with_flags):
|
|
||||||
if is_action:
|
|
||||||
message_details.append((timestamp, name, content, is_action))
|
|
||||||
continue
|
|
||||||
|
|
||||||
percentile = i / n_messages
|
|
||||||
original_len = len(content)
|
|
||||||
limit = -1
|
|
||||||
|
|
||||||
if percentile < 0.2:
|
|
||||||
limit = 50
|
|
||||||
replace_content = "......(记不清了)"
|
|
||||||
elif percentile < 0.5:
|
|
||||||
limit = 100
|
|
||||||
replace_content = "......(有点记不清了)"
|
|
||||||
elif percentile < 0.7:
|
|
||||||
limit = 200
|
|
||||||
replace_content = "......(内容太长了)"
|
|
||||||
elif percentile < 1.0:
|
|
||||||
limit = 300
|
|
||||||
replace_content = "......(太长了)"
|
|
||||||
|
|
||||||
truncated_content = content
|
|
||||||
if 0 < limit < original_len:
|
|
||||||
truncated_content = f"{content[:limit]}{replace_content}"
|
|
||||||
|
|
||||||
message_details.append((timestamp, name, truncated_content, is_action))
|
|
||||||
else:
|
|
||||||
message_details = message_details_with_flags
|
|
||||||
|
|
||||||
# 合并连续消息
|
|
||||||
merged_messages = []
|
|
||||||
if merge_messages and message_details:
|
|
||||||
current_merge = {
|
|
||||||
"name": message_details[0][1],
|
|
||||||
"start_time": message_details[0][0],
|
|
||||||
"end_time": message_details[0][0],
|
|
||||||
"content": [message_details[0][2]],
|
|
||||||
"is_action": message_details[0][3]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in range(1, len(message_details)):
|
|
||||||
timestamp, name, content, is_action = message_details[i]
|
|
||||||
|
|
||||||
if is_action or current_merge["is_action"]:
|
|
||||||
merged_messages.append(current_merge)
|
|
||||||
current_merge = {
|
|
||||||
"name": name,
|
|
||||||
"start_time": timestamp,
|
|
||||||
"end_time": timestamp,
|
|
||||||
"content": [content],
|
|
||||||
"is_action": is_action
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
|
|
||||||
if name == current_merge["name"] and (timestamp - current_merge["end_time"] <= 60):
|
|
||||||
current_merge["content"].append(content)
|
|
||||||
current_merge["end_time"] = timestamp
|
|
||||||
else:
|
|
||||||
merged_messages.append(current_merge)
|
|
||||||
current_merge = {
|
|
||||||
"name": name,
|
|
||||||
"start_time": timestamp,
|
|
||||||
"end_time": timestamp,
|
|
||||||
"content": [content],
|
|
||||||
"is_action": is_action
|
|
||||||
}
|
|
||||||
merged_messages.append(current_merge)
|
|
||||||
elif message_details:
|
|
||||||
for timestamp, name, content, is_action in message_details:
|
|
||||||
merged_messages.append(
|
|
||||||
{
|
|
||||||
"name": name,
|
|
||||||
"start_time": timestamp,
|
|
||||||
"end_time": timestamp,
|
|
||||||
"content": [content],
|
|
||||||
"is_action": is_action
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 格式化为字符串
|
|
||||||
output_lines = []
|
|
||||||
for merged in merged_messages:
|
|
||||||
readable_time = translate_timestamp_to_human_readable(merged["start_time"], mode=timestamp_mode)
|
|
||||||
|
|
||||||
if merged["is_action"]:
|
|
||||||
output_lines.append(f"{readable_time}, {merged['content'][0]}")
|
|
||||||
else:
|
|
||||||
header = f"{readable_time}, {merged['name']} :"
|
|
||||||
output_lines.append(header)
|
|
||||||
for line in merged["content"]:
|
|
||||||
stripped_line = line.strip()
|
|
||||||
if stripped_line:
|
|
||||||
if stripped_line.endswith("。"):
|
|
||||||
stripped_line = stripped_line[:-1]
|
|
||||||
if not stripped_line.endswith("(内容太长)"):
|
|
||||||
output_lines.append(f"{stripped_line}")
|
|
||||||
else:
|
|
||||||
output_lines.append(stripped_line)
|
|
||||||
output_lines.append("\n")
|
|
||||||
|
|
||||||
formatted_string = "".join(output_lines).strip()
|
|
||||||
return formatted_string, [(t, n, c) for t, n, c, is_action in message_details if not is_action]
|
|
||||||
|
|
||||||
def build_readable_messages(
|
|
||||||
messages: List[Dict[str, Any]],
|
|
||||||
replace_bot_name: bool = True,
|
|
||||||
merge_messages: bool = False,
|
|
||||||
timestamp_mode: str = "relative",
|
|
||||||
read_mark: float = 0.0,
|
|
||||||
truncate: bool = False,
|
|
||||||
show_actions: bool = False,
|
|
||||||
) -> str:
|
|
||||||
"""将消息列表转换为可读的文本格式"""
|
|
||||||
copy_messages = [msg.copy() for msg in messages]
|
|
||||||
|
|
||||||
if show_actions and copy_messages:
|
|
||||||
min_time = min(msg.get("time", 0) for msg in copy_messages)
|
|
||||||
max_time = max(msg.get("time", 0) for msg in copy_messages)
|
|
||||||
chat_id = copy_messages[0].get("chat_id") if copy_messages else None
|
|
||||||
|
|
||||||
actions = ActionRecords.select().where(
|
|
||||||
(ActionRecords.time >= min_time) &
|
|
||||||
(ActionRecords.time <= max_time) &
|
|
||||||
(ActionRecords.chat_id == chat_id)
|
|
||||||
).order_by(ActionRecords.time)
|
|
||||||
|
|
||||||
for action in actions:
|
|
||||||
if action.action_build_into_prompt:
|
|
||||||
action_msg = {
|
|
||||||
"time": action.time,
|
|
||||||
"user_id": global_config.bot.qq_account,
|
|
||||||
"user_nickname": global_config.bot.nickname,
|
|
||||||
"user_cardname": "",
|
|
||||||
"processed_plain_text": f"{action.action_prompt_display}",
|
|
||||||
"display_message": f"{action.action_prompt_display}",
|
|
||||||
"chat_info_platform": action.chat_info_platform,
|
|
||||||
"is_action_record": True,
|
|
||||||
"action_name": action.action_name,
|
|
||||||
}
|
|
||||||
copy_messages.append(action_msg)
|
|
||||||
|
|
||||||
copy_messages.sort(key=lambda x: x.get("time", 0))
|
|
||||||
|
|
||||||
if read_mark <= 0:
|
|
||||||
formatted_string, _ = _build_readable_messages_internal(
|
|
||||||
copy_messages, replace_bot_name, merge_messages, timestamp_mode, truncate
|
|
||||||
)
|
|
||||||
return formatted_string
|
|
||||||
else:
|
|
||||||
messages_before_mark = [msg for msg in copy_messages if msg.get("time", 0) <= read_mark]
|
|
||||||
messages_after_mark = [msg for msg in copy_messages if msg.get("time", 0) > read_mark]
|
|
||||||
|
|
||||||
formatted_before, _ = _build_readable_messages_internal(
|
|
||||||
messages_before_mark, replace_bot_name, merge_messages, timestamp_mode, truncate
|
|
||||||
)
|
|
||||||
formatted_after, _ = _build_readable_messages_internal(
|
|
||||||
messages_after_mark,
|
|
||||||
replace_bot_name,
|
|
||||||
merge_messages,
|
|
||||||
timestamp_mode,
|
|
||||||
)
|
|
||||||
|
|
||||||
read_mark_line = "\n--- 以上消息是你已经看过---\n--- 请关注以下未读的新消息---\n"
|
|
||||||
|
|
||||||
if formatted_before and formatted_after:
|
|
||||||
return f"{formatted_before}{read_mark_line}{formatted_after}"
|
|
||||||
elif formatted_before:
|
|
||||||
return f"{formatted_before}{read_mark_line}"
|
|
||||||
elif formatted_after:
|
|
||||||
return f"{read_mark_line}{formatted_after}"
|
|
||||||
else:
|
|
||||||
return read_mark_line.strip()
|
|
||||||
|
|
||||||
async def test_relationship_processor():
|
|
||||||
"""测试关系处理器的功能"""
|
|
||||||
|
|
||||||
# 测试10次
|
|
||||||
for i in range(10):
|
|
||||||
print(f"\n=== 测试 {i+1} ===")
|
|
||||||
|
|
||||||
# 获取随机消息
|
|
||||||
current_time = time.time()
|
|
||||||
start_time = current_time - 864000 # 10天前
|
|
||||||
messages = get_raw_msg_by_timestamp_random(start_time, current_time, limit=25)
|
|
||||||
|
|
||||||
if not messages:
|
|
||||||
print("没有找到消息,跳过此次测试")
|
|
||||||
continue
|
|
||||||
|
|
||||||
chat_id = messages[0]["chat_id"]
|
|
||||||
|
|
||||||
# 构建可读消息
|
|
||||||
chat_observe_info = build_readable_messages(
|
|
||||||
messages,
|
|
||||||
replace_bot_name=True,
|
|
||||||
timestamp_mode="normal_no_YMD",
|
|
||||||
truncate=True,
|
|
||||||
show_actions=True,
|
|
||||||
)
|
|
||||||
# print(chat_observe_info)
|
|
||||||
# 创建观察对象
|
|
||||||
processor = RelationshipProcessor(chat_id)
|
|
||||||
observation = ChattingObservation(chat_id)
|
|
||||||
observation.talking_message_str = chat_observe_info
|
|
||||||
observation.talking_message = messages # 设置消息列表
|
|
||||||
observation.person_list = await get_person_id_list(messages) # 使用get_person_id_list获取person_list
|
|
||||||
|
|
||||||
# 处理关系
|
|
||||||
result = await processor.process_info([observation])
|
|
||||||
|
|
||||||
if result:
|
|
||||||
print("\n关系识别结果:")
|
|
||||||
print(result[0].get_processed_info())
|
|
||||||
else:
|
|
||||||
print("关系识别失败")
|
|
||||||
|
|
||||||
# 等待一下,避免请求过快
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(test_relationship_processor())
|
|
||||||
Reference in New Issue
Block a user