Merge branch 'dev' into dev-api-ada to resolve conflicts
This commit is contained in:
12
.github/workflows/docker-image.yml
vendored
12
.github/workflows/docker-image.yml
vendored
@@ -11,12 +11,13 @@ on:
|
|||||||
- "v*"
|
- "v*"
|
||||||
- "*.*.*"
|
- "*.*.*"
|
||||||
- "*.*.*-*"
|
- "*.*.*-*"
|
||||||
|
workflow_dispatch: # 允许手动触发工作流
|
||||||
|
|
||||||
# Workflow's jobs
|
# Workflow's jobs
|
||||||
jobs:
|
jobs:
|
||||||
build-amd64:
|
build-amd64:
|
||||||
name: Build AMD64 Image
|
name: Build AMD64 Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
outputs:
|
outputs:
|
||||||
digest: ${{ steps.build.outputs.digest }}
|
digest: ${{ steps.build.outputs.digest }}
|
||||||
steps:
|
steps:
|
||||||
@@ -69,7 +70,7 @@ jobs:
|
|||||||
|
|
||||||
build-arm64:
|
build-arm64:
|
||||||
name: Build ARM64 Image
|
name: Build ARM64 Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04-arm
|
||||||
outputs:
|
outputs:
|
||||||
digest: ${{ steps.build.outputs.digest }}
|
digest: ${{ steps.build.outputs.digest }}
|
||||||
steps:
|
steps:
|
||||||
@@ -85,11 +86,6 @@ jobs:
|
|||||||
- name: Clone lpmm
|
- name: Clone lpmm
|
||||||
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
|
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: arm64
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -127,7 +123,7 @@ jobs:
|
|||||||
|
|
||||||
create-manifest:
|
create-manifest:
|
||||||
name: Create Multi-Arch Manifest
|
name: Create Multi-Arch Manifest
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- build-amd64
|
- build-amd64
|
||||||
- build-arm64
|
- build-arm64
|
||||||
|
|||||||
12
.github/workflows/ruff.yml
vendored
12
.github/workflows/ruff.yml
vendored
@@ -1,12 +1,12 @@
|
|||||||
name: Ruff
|
name: Ruff
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
# push:
|
||||||
branches:
|
# branches:
|
||||||
- main
|
# - main
|
||||||
- dev
|
# - dev
|
||||||
- dev-refactor # 例如:匹配所有以 feature/ 开头的分支
|
# - dev-refactor # 例如:匹配所有以 feature/ 开头的分支
|
||||||
# 添加你希望触发此 workflow 的其他分支
|
# # 添加你希望触发此 workflow 的其他分支
|
||||||
workflow_dispatch: # 允许手动触发工作流
|
workflow_dispatch: # 允许手动触发工作流
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -321,4 +321,5 @@ run_pet.bat
|
|||||||
|
|
||||||
config.toml
|
config.toml
|
||||||
|
|
||||||
interested_rates.txt
|
interested_rates.txt
|
||||||
|
MaiBot.code-workspace
|
||||||
|
|||||||
@@ -1,14 +1,28 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.9.1] - 2025-7-25
|
## [0.9.1] - 2025-7-26
|
||||||
|
|
||||||
|
### 主要修复和优化
|
||||||
|
|
||||||
|
- 优化回复意愿
|
||||||
|
- 优化专注模式回复频率
|
||||||
|
- 优化关键词提取
|
||||||
|
- 修复部分模型产生的400问题
|
||||||
|
|
||||||
|
### 细节优化
|
||||||
|
|
||||||
|
- 修复reply导致的planner异常空跳
|
||||||
- 修复表达方式迁移空目录问题
|
- 修复表达方式迁移空目录问题
|
||||||
- 修复reply_to空字段问题
|
- 修复reply_to空字段问题
|
||||||
|
- 无可用动作导致的空plan问题
|
||||||
|
- 修复人格未压缩导致产生句号分割
|
||||||
- 将metioned bot 和 at应用到focus prompt中
|
- 将metioned bot 和 at应用到focus prompt中
|
||||||
|
- 更好的兴趣度计算
|
||||||
|
- 修复部分模型由于enable_thinking导致的400问题
|
||||||
|
- 移除dependency_manager
|
||||||
|
|
||||||
|
|
||||||
|
## [0.9.0] - 2025-7-24
|
||||||
## [0.9.0] - 2025-7-25
|
|
||||||
|
|
||||||
### 摘要
|
### 摘要
|
||||||
MaiBot 0.9.0 重磅升级!本版本带来两大核心突破:**全面重构的插件系统**提供更强大的扩展能力和管理功能;**normal和focus模式统一化处理**大幅简化架构并提升性能。同时新增s4u prompt模式优化、语音消息支持、全新情绪系统和mais4u直播互动功能,为MaiBot带来更自然、更智能的交互体验!
|
MaiBot 0.9.0 重磅升级!本版本带来两大核心突破:**全面重构的插件系统**提供更强大的扩展能力和管理功能;**normal和focus模式统一化处理**大幅简化架构并提升性能。同时新增s4u prompt模式优化、语音消息支持、全新情绪系统和mais4u直播互动功能,为MaiBot带来更自然、更智能的交互体验!
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
6. 增加了插件和组件管理的API。
|
6. 增加了插件和组件管理的API。
|
||||||
7. `BaseCommand`的`execute`方法现在返回一个三元组,包含是否执行成功、可选的回复消息和是否拦截消息。
|
7. `BaseCommand`的`execute`方法现在返回一个三元组,包含是否执行成功、可选的回复消息和是否拦截消息。
|
||||||
- 这意味着你终于可以动态控制是否继续后续消息的处理了。
|
- 这意味着你终于可以动态控制是否继续后续消息的处理了。
|
||||||
|
8. 移除了dependency_manager,但是依然保留了`python_dependencies`属性,等待后续重构。
|
||||||
|
- 一并移除了文档有关manager的内容。
|
||||||
|
|
||||||
# 插件系统修改
|
# 插件系统修改
|
||||||
1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)**
|
1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)**
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ExampleAction(BaseAction):
|
|||||||
action_name = "example_action" # 动作的唯一标识符
|
action_name = "example_action" # 动作的唯一标识符
|
||||||
action_description = "这是一个示例动作" # 动作描述
|
action_description = "这是一个示例动作" # 动作描述
|
||||||
activation_type = ActionActivationType.ALWAYS # 这里以 ALWAYS 为例
|
activation_type = ActionActivationType.ALWAYS # 这里以 ALWAYS 为例
|
||||||
mode_enable = ChatMode.ALL # 这里以 ALL 为例
|
mode_enable = ChatMode.ALL # 一般取ALL,表示在所有聊天模式下都可用
|
||||||
associated_types = ["text", "emoji", ...] # 关联类型
|
associated_types = ["text", "emoji", ...] # 关联类型
|
||||||
parallel_action = False # 是否允许与其他Action并行执行
|
parallel_action = False # 是否允许与其他Action并行执行
|
||||||
action_parameters = {"param1": "参数1的说明", "param2": "参数2的说明", ...}
|
action_parameters = {"param1": "参数1的说明", "param2": "参数2的说明", ...}
|
||||||
@@ -60,7 +60,7 @@ class ExampleAction(BaseAction):
|
|||||||
**请知悉,对于不同的处理器,其支持的消息类型可能会有所不同。在开发时请注意。**
|
**请知悉,对于不同的处理器,其支持的消息类型可能会有所不同。在开发时请注意。**
|
||||||
|
|
||||||
#### action_parameters: 该Action的参数说明。
|
#### action_parameters: 该Action的参数说明。
|
||||||
这是一个字典,键为参数名,值为参数说明。这个字段可以帮助LLM理解如何使用这个Action,并由LLM返回对应的参数,最后传递到 Action 的 action_data 属性中。其格式与你定义的格式完全相同 **(除非LLM哈气了,返回了错误的内容)**。
|
这是一个字典,键为参数名,值为参数说明。这个字段可以帮助LLM理解如何使用这个Action,并由LLM返回对应的参数,最后传递到 Action 的 **`action_data`** 属性中。其格式与你定义的格式完全相同 **(除非LLM哈气了,返回了错误的内容)**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -180,6 +180,8 @@ class GreetingAction(BaseAction):
|
|||||||
return True, "发送了问候"
|
return True, "发送了问候"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
一个完整的使用`ActionActivationType.KEYWORD`的例子请参考`plugins/hello_world_plugin`中的`ByeAction`。
|
||||||
|
|
||||||
#### 第二层:使用决策
|
#### 第二层:使用决策
|
||||||
|
|
||||||
**在Action被激活后,使用条件决定麦麦什么时候会"选择"使用这个Action**。
|
**在Action被激活后,使用条件决定麦麦什么时候会"选择"使用这个Action**。
|
||||||
@@ -268,7 +270,6 @@ action_message为一个字典,包含的键值对如下(省略了不必要的
|
|||||||
## Action 内置方法说明
|
## Action 内置方法说明
|
||||||
```python
|
```python
|
||||||
class BaseAction:
|
class BaseAction:
|
||||||
# 配置相关
|
|
||||||
def get_config(self, key: str, default=None):
|
def get_config(self, key: str, default=None):
|
||||||
"""获取插件配置值,使用嵌套键访问"""
|
"""获取插件配置值,使用嵌套键访问"""
|
||||||
|
|
||||||
|
|||||||
@@ -5,147 +5,126 @@
|
|||||||
## 导入方式
|
## 导入方式
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import chat_api
|
from src.plugin_system import chat_api
|
||||||
# 或者
|
# 或者
|
||||||
from src.plugin_system.apis.chat_api import ChatManager as chat
|
from src.plugin_system.apis import chat_api
|
||||||
|
```
|
||||||
|
|
||||||
|
一种**Deprecated**方式:
|
||||||
|
```python
|
||||||
|
from src.plugin_system.apis.chat_api import ChatManager
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. 获取聊天流
|
### 1. 获取所有的聊天流
|
||||||
|
|
||||||
#### `get_all_streams(platform: str = "qq") -> List[ChatStream]`
|
```python
|
||||||
获取所有聊天流
|
def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
|
```
|
||||||
|
|
||||||
**参数:**
|
**Args**:
|
||||||
- `platform`:平台筛选,默认为"qq"
|
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的聊天流。
|
||||||
|
|
||||||
**返回:**
|
**Returns**:
|
||||||
- `List[ChatStream]`:聊天流列表
|
- `List[ChatStream]`:聊天流列表
|
||||||
|
|
||||||
**示例:**
|
### 2. 获取群聊聊天流
|
||||||
|
|
||||||
```python
|
```python
|
||||||
streams = chat_api.get_all_streams()
|
def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
for stream in streams:
|
|
||||||
print(f"聊天流ID: {stream.stream_id}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_group_streams(platform: str = "qq") -> List[ChatStream]`
|
**Args**:
|
||||||
获取所有群聊聊天流
|
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。
|
||||||
|
|
||||||
**参数:**
|
**Returns**:
|
||||||
- `platform`:平台筛选,默认为"qq"
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `List[ChatStream]`:群聊聊天流列表
|
- `List[ChatStream]`:群聊聊天流列表
|
||||||
|
|
||||||
#### `get_private_streams(platform: str = "qq") -> List[ChatStream]`
|
### 3. 获取私聊聊天流
|
||||||
获取所有私聊聊天流
|
|
||||||
|
|
||||||
**参数:**
|
```python
|
||||||
- `platform`:平台筛选,默认为"qq"
|
def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
|
```
|
||||||
|
|
||||||
**返回:**
|
**Args**:
|
||||||
|
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
- `List[ChatStream]`:私聊聊天流列表
|
- `List[ChatStream]`:私聊聊天流列表
|
||||||
|
|
||||||
### 2. 查找特定聊天流
|
### 4. 根据群ID获取聊天流
|
||||||
|
|
||||||
#### `get_stream_by_group_id(group_id: str, platform: str = "qq") -> Optional[ChatStream]`
|
```python
|
||||||
根据群ID获取聊天流
|
def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
|
||||||
|
```
|
||||||
|
|
||||||
**参数:**
|
**Args**:
|
||||||
- `group_id`:群聊ID
|
- `group_id`:群聊ID
|
||||||
- `platform`:平台,默认为"qq"
|
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。
|
||||||
|
|
||||||
**返回:**
|
**Returns**:
|
||||||
- `Optional[ChatStream]`:聊天流对象,如果未找到返回None
|
- `Optional[ChatStream]`:聊天流对象,如果未找到返回None
|
||||||
|
|
||||||
**示例:**
|
### 5. 根据用户ID获取私聊流
|
||||||
|
|
||||||
```python
|
```python
|
||||||
chat_stream = chat_api.get_stream_by_group_id("123456789")
|
def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
|
||||||
if chat_stream:
|
|
||||||
print(f"找到群聊: {chat_stream.group_info.group_name}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_stream_by_user_id(user_id: str, platform: str = "qq") -> Optional[ChatStream]`
|
**Args**:
|
||||||
根据用户ID获取私聊流
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `user_id`:用户ID
|
- `user_id`:用户ID
|
||||||
- `platform`:平台,默认为"qq"
|
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。
|
||||||
|
|
||||||
**返回:**
|
**Returns**:
|
||||||
- `Optional[ChatStream]`:聊天流对象,如果未找到返回None
|
- `Optional[ChatStream]`:聊天流对象,如果未找到返回None
|
||||||
|
|
||||||
### 3. 聊天流信息查询
|
### 6. 获取聊天流类型
|
||||||
|
|
||||||
#### `get_stream_type(chat_stream: ChatStream) -> str`
|
```python
|
||||||
获取聊天流类型
|
def get_stream_type(chat_stream: ChatStream) -> str:
|
||||||
|
```
|
||||||
|
|
||||||
**参数:**
|
**Args**:
|
||||||
- `chat_stream`:聊天流对象
|
- `chat_stream`:聊天流对象
|
||||||
|
|
||||||
**返回:**
|
**Returns**:
|
||||||
- `str`:聊天类型 ("group", "private", "unknown")
|
- `str`:聊天流类型,可能的值包括`private`(私聊流),`group`(群聊流)以及`unknown`(未知类型)。
|
||||||
|
|
||||||
#### `get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]`
|
### 7. 获取聊天流信息
|
||||||
获取聊天流详细信息
|
|
||||||
|
|
||||||
**参数:**
|
```python
|
||||||
|
def get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Args**:
|
||||||
- `chat_stream`:聊天流对象
|
- `chat_stream`:聊天流对象
|
||||||
|
|
||||||
**返回:**
|
**Returns**:
|
||||||
- `Dict[str, Any]`:聊天流信息字典,包含stream_id、platform、type等信息
|
- `Dict[str, Any]`:聊天流的详细信息,包括但不限于:
|
||||||
|
- `stream_id`:聊天流ID
|
||||||
|
- `platform`:平台名称
|
||||||
|
- `type`:聊天流类型
|
||||||
|
- `group_id`:群聊ID
|
||||||
|
- `group_name`:群聊名称
|
||||||
|
- `user_id`:用户ID
|
||||||
|
- `user_name`:用户名称
|
||||||
|
|
||||||
|
### 8. 获取聊天流统计摘要
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
info = chat_api.get_stream_info(chat_stream)
|
def get_streams_summary() -> Dict[str, int]:
|
||||||
print(f"聊天类型: {info['type']}")
|
|
||||||
print(f"平台: {info['platform']}")
|
|
||||||
if info['type'] == 'group':
|
|
||||||
print(f"群ID: {info['group_id']}")
|
|
||||||
print(f"群名: {info['group_name']}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_streams_summary() -> Dict[str, int]`
|
**Returns**:
|
||||||
获取聊天流统计信息
|
- `Dict[str, int]`:聊天流统计信息摘要,包含以下键:
|
||||||
|
- `total_streams`:总聊天流数量
|
||||||
|
- `group_streams`:群聊流数量
|
||||||
|
- `private_streams`:私聊流数量
|
||||||
|
- `qq_streams`:QQ平台流数量
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Dict[str, int]`:包含各平台群聊和私聊数量的统计字典
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 基础用法
|
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import chat_api
|
|
||||||
|
|
||||||
# 获取所有群聊
|
|
||||||
group_streams = chat_api.get_group_streams()
|
|
||||||
print(f"共有 {len(group_streams)} 个群聊")
|
|
||||||
|
|
||||||
# 查找特定群聊
|
|
||||||
target_group = chat_api.get_stream_by_group_id("123456789")
|
|
||||||
if target_group:
|
|
||||||
group_info = chat_api.get_stream_info(target_group)
|
|
||||||
print(f"群名: {group_info['group_name']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 遍历所有聊天流
|
|
||||||
```python
|
|
||||||
# 获取所有聊天流并分类处理
|
|
||||||
all_streams = chat_api.get_all_streams()
|
|
||||||
|
|
||||||
for stream in all_streams:
|
|
||||||
stream_type = chat_api.get_stream_type(stream)
|
|
||||||
if stream_type == "group":
|
|
||||||
print(f"群聊: {stream.group_info.group_name}")
|
|
||||||
elif stream_type == "private":
|
|
||||||
print(f"私聊: {stream.user_info.user_nickname}")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. 所有函数都有错误处理,失败时会记录日志
|
1. 大部分函数在参数不合法时候会抛出异常,请确保你的程序进行了捕获。
|
||||||
2. 查询函数返回None或空列表时表示未找到结果
|
2. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等。
|
||||||
3. `platform`参数通常为"qq",也可能支持其他平台
|
|
||||||
4. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等
|
|
||||||
180
docs/plugins/api/component-manage-api.md
Normal file
180
docs/plugins/api/component-manage-api.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# 组件管理API
|
||||||
|
|
||||||
|
组件管理API模块提供了对插件组件的查询和管理功能,使得插件能够获取和使用组件相关的信息。
|
||||||
|
|
||||||
|
## 导入方式
|
||||||
|
```python
|
||||||
|
from src.plugin_system.apis import component_manage_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import component_manage_api
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
组件管理API主要提供以下功能:
|
||||||
|
- **插件信息查询** - 获取所有插件或指定插件的信息。
|
||||||
|
- **组件查询** - 按名称或类型查询组件信息。
|
||||||
|
- **组件管理** - 启用或禁用组件,支持全局和局部操作。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 1. 获取所有插件信息
|
||||||
|
```python
|
||||||
|
def get_all_plugin_info() -> Dict[str, PluginInfo]:
|
||||||
|
```
|
||||||
|
获取所有插件的信息。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Dict[str, PluginInfo]` - 包含所有插件信息的字典,键为插件名称,值为 `PluginInfo` 对象。
|
||||||
|
|
||||||
|
### 2. 获取指定插件信息
|
||||||
|
```python
|
||||||
|
def get_plugin_info(plugin_name: str) -> Optional[PluginInfo]:
|
||||||
|
```
|
||||||
|
获取指定插件的信息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `plugin_name` (str): 插件名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Optional[PluginInfo]`: 插件信息对象,如果插件不存在则返回 `None`。
|
||||||
|
|
||||||
|
### 3. 获取指定组件信息
|
||||||
|
```python
|
||||||
|
def get_component_info(component_name: str, component_type: ComponentType) -> Optional[Union[CommandInfo, ActionInfo, EventHandlerInfo]]:
|
||||||
|
```
|
||||||
|
获取指定组件的信息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `component_name` (str): 组件名称。
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Optional[Union[CommandInfo, ActionInfo, EventHandlerInfo]]`: 组件信息对象,如果组件不存在则返回 `None`。
|
||||||
|
|
||||||
|
### 4. 获取指定类型的所有组件信息
|
||||||
|
```python
|
||||||
|
def get_components_info_by_type(component_type: ComponentType) -> Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]:
|
||||||
|
```
|
||||||
|
获取指定类型的所有组件信息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]`: 包含指定类型组件信息的字典,键为组件名称,值为对应的组件信息对象。
|
||||||
|
|
||||||
|
### 5. 获取指定类型的所有启用的组件信息
|
||||||
|
```python
|
||||||
|
def get_enabled_components_info_by_type(component_type: ComponentType) -> Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]:
|
||||||
|
```
|
||||||
|
获取指定类型的所有启用的组件信息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]`: 包含指定类型启用组件信息的字典,键为组件名称,值为对应的组件信息对象。
|
||||||
|
|
||||||
|
### 6. 获取指定 Action 的注册信息
|
||||||
|
```python
|
||||||
|
def get_registered_action_info(action_name: str) -> Optional[ActionInfo]:
|
||||||
|
```
|
||||||
|
获取指定 Action 的注册信息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `action_name` (str): Action 名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Optional[ActionInfo]` - Action 信息对象,如果 Action 不存在则返回 `None`。
|
||||||
|
|
||||||
|
### 7. 获取指定 Command 的注册信息
|
||||||
|
```python
|
||||||
|
def get_registered_command_info(command_name: str) -> Optional[CommandInfo]:
|
||||||
|
```
|
||||||
|
获取指定 Command 的注册信息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `command_name` (str): Command 名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Optional[CommandInfo]` - Command 信息对象,如果 Command 不存在则返回 `None`。
|
||||||
|
|
||||||
|
### 8. 获取指定 EventHandler 的注册信息
|
||||||
|
```python
|
||||||
|
def get_registered_event_handler_info(event_handler_name: str) -> Optional[EventHandlerInfo]:
|
||||||
|
```
|
||||||
|
获取指定 EventHandler 的注册信息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `event_handler_name` (str): EventHandler 名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Optional[EventHandlerInfo]` - EventHandler 信息对象,如果 EventHandler 不存在则返回 `None`。
|
||||||
|
|
||||||
|
### 9. 全局启用指定组件
|
||||||
|
```python
|
||||||
|
def globally_enable_component(component_name: str, component_type: ComponentType) -> bool:
|
||||||
|
```
|
||||||
|
全局启用指定组件。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `component_name` (str): 组件名称。
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `bool` - 启用成功返回 `True`,否则返回 `False`。
|
||||||
|
|
||||||
|
### 10. 全局禁用指定组件
|
||||||
|
```python
|
||||||
|
async def globally_disable_component(component_name: str, component_type: ComponentType) -> bool:
|
||||||
|
```
|
||||||
|
全局禁用指定组件。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `component_name` (str): 组件名称。
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `bool` - 禁用成功返回 `True`,否则返回 `False`。
|
||||||
|
|
||||||
|
### 11. 局部启用指定组件
|
||||||
|
```python
|
||||||
|
def locally_enable_component(component_name: str, component_type: ComponentType, stream_id: str) -> bool:
|
||||||
|
```
|
||||||
|
局部启用指定组件。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `component_name` (str): 组件名称。
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
- `stream_id` (str): 消息流 ID。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `bool` - 启用成功返回 `True`,否则返回 `False`。
|
||||||
|
|
||||||
|
### 12. 局部禁用指定组件
|
||||||
|
```python
|
||||||
|
def locally_disable_component(component_name: str, component_type: ComponentType, stream_id: str) -> bool:
|
||||||
|
```
|
||||||
|
局部禁用指定组件。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `component_name` (str): 组件名称。
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
- `stream_id` (str): 消息流 ID。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `bool` - 禁用成功返回 `True`,否则返回 `False`。
|
||||||
|
|
||||||
|
### 13. 获取指定消息流中禁用的组件列表
|
||||||
|
```python
|
||||||
|
def get_locally_disabled_components(stream_id: str, component_type: ComponentType) -> list[str]:
|
||||||
|
```
|
||||||
|
获取指定消息流中禁用的组件列表。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `stream_id` (str): 消息流 ID。
|
||||||
|
- `component_type` (ComponentType): 组件类型。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `list[str]` - 禁用的组件名称列表。
|
||||||
@@ -6,178 +6,47 @@
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import config_api
|
from src.plugin_system.apis import config_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import config_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. 配置访问
|
### 1. 访问全局配置
|
||||||
|
|
||||||
#### `get_global_config(key: str, default: Any = None) -> Any`
|
|
||||||
安全地从全局配置中获取一个值
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `key`:配置键名,支持嵌套访问如 "section.subsection.key"
|
|
||||||
- `default`:如果配置不存在时返回的默认值
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Any`:配置值或默认值
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
# 获取机器人昵称
|
def get_global_config(key: str, default: Any = None) -> Any:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Args**:
|
||||||
|
- `key`: 命名空间式配置键名,使用嵌套访问,如 "section.subsection.key",大小写敏感
|
||||||
|
- `default`: 如果配置不存在时返回的默认值
|
||||||
|
|
||||||
|
**Returns**:
|
||||||
|
- `Any`: 配置值或默认值
|
||||||
|
|
||||||
|
#### 示例:
|
||||||
|
获取机器人昵称
|
||||||
|
```python
|
||||||
bot_name = config_api.get_global_config("bot.nickname", "MaiBot")
|
bot_name = config_api.get_global_config("bot.nickname", "MaiBot")
|
||||||
|
|
||||||
# 获取嵌套配置
|
|
||||||
llm_model = config_api.get_global_config("model.default.model_name", "gpt-3.5-turbo")
|
|
||||||
|
|
||||||
# 获取不存在的配置
|
|
||||||
unknown_config = config_api.get_global_config("unknown.config", "默认值")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any`
|
### 2. 获取插件配置
|
||||||
从插件配置中获取值,支持嵌套键访问
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `plugin_config`:插件配置字典
|
|
||||||
- `key`:配置键名,支持嵌套访问如 "section.subsection.key"
|
|
||||||
- `default`:如果配置不存在时返回的默认值
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Any`:配置值或默认值
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
# 在插件中使用
|
def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any:
|
||||||
class MyPlugin(BasePlugin):
|
|
||||||
async def handle_action(self, action_data, chat_stream):
|
|
||||||
# 获取插件配置
|
|
||||||
api_key = config_api.get_plugin_config(self.config, "api.key", "")
|
|
||||||
timeout = config_api.get_plugin_config(self.config, "timeout", 30)
|
|
||||||
|
|
||||||
if not api_key:
|
|
||||||
logger.warning("API密钥未配置")
|
|
||||||
return False
|
|
||||||
```
|
```
|
||||||
|
**Args**:
|
||||||
|
- `plugin_config`: 插件配置字典
|
||||||
|
- `key`: 配置键名,支持嵌套访问如 "section.subsection.key",大小写敏感
|
||||||
|
- `default`: 如果配置不存在时返回的默认值
|
||||||
|
|
||||||
### 2. 用户信息API
|
**Returns**:
|
||||||
|
- `Any`: 配置值或默认值
|
||||||
#### `get_user_id_by_person_name(person_name: str) -> tuple[str, str]`
|
|
||||||
根据用户名获取用户ID
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `person_name`:用户名
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `tuple[str, str]`:(平台, 用户ID)
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
platform, user_id = await config_api.get_user_id_by_person_name("张三")
|
|
||||||
if platform and user_id:
|
|
||||||
print(f"用户张三在{platform}平台的ID是{user_id}")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `get_person_info(person_id: str, key: str, default: Any = None) -> Any`
|
|
||||||
获取用户信息
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `person_id`:用户ID
|
|
||||||
- `key`:信息键名
|
|
||||||
- `default`:默认值
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Any`:用户信息值或默认值
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
# 获取用户昵称
|
|
||||||
nickname = await config_api.get_person_info(person_id, "nickname", "未知用户")
|
|
||||||
|
|
||||||
# 获取用户印象
|
|
||||||
impression = await config_api.get_person_info(person_id, "impression", "")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 配置驱动的插件开发
|
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import config_api
|
|
||||||
from src.plugin_system.base import BasePlugin
|
|
||||||
|
|
||||||
class WeatherPlugin(BasePlugin):
|
|
||||||
async def handle_action(self, action_data, chat_stream):
|
|
||||||
# 从全局配置获取API配置
|
|
||||||
api_endpoint = config_api.get_global_config("weather.api_endpoint", "")
|
|
||||||
default_city = config_api.get_global_config("weather.default_city", "北京")
|
|
||||||
|
|
||||||
# 从插件配置获取特定设置
|
|
||||||
api_key = config_api.get_plugin_config(self.config, "api_key", "")
|
|
||||||
timeout = config_api.get_plugin_config(self.config, "timeout", 10)
|
|
||||||
|
|
||||||
if not api_key:
|
|
||||||
return {"success": False, "message": "Weather API密钥未配置"}
|
|
||||||
|
|
||||||
# 使用配置进行天气查询...
|
|
||||||
return {"success": True, "message": f"{default_city}今天天气晴朗"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 用户信息查询
|
|
||||||
```python
|
|
||||||
async def get_user_by_name(user_name: str):
|
|
||||||
"""根据用户名获取完整的用户信息"""
|
|
||||||
|
|
||||||
# 获取用户的平台和ID
|
|
||||||
platform, user_id = await config_api.get_user_id_by_person_name(user_name)
|
|
||||||
|
|
||||||
if not platform or not user_id:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 构建person_id
|
|
||||||
from src.person_info.person_info import PersonInfoManager
|
|
||||||
person_id = PersonInfoManager.get_person_id(platform, user_id)
|
|
||||||
|
|
||||||
# 获取用户详细信息
|
|
||||||
nickname = await config_api.get_person_info(person_id, "nickname", user_name)
|
|
||||||
impression = await config_api.get_person_info(person_id, "impression", "")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"platform": platform,
|
|
||||||
"user_id": user_id,
|
|
||||||
"nickname": nickname,
|
|
||||||
"impression": impression
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 配置键名说明
|
|
||||||
|
|
||||||
### 常用全局配置键
|
|
||||||
- `bot.nickname`:机器人昵称
|
|
||||||
- `bot.qq_account`:机器人QQ号
|
|
||||||
- `model.default`:默认LLM模型配置
|
|
||||||
- `database.path`:数据库路径
|
|
||||||
|
|
||||||
### 嵌套配置访问
|
|
||||||
配置支持点号分隔的嵌套访问:
|
|
||||||
```python
|
|
||||||
# config.toml 中的配置:
|
|
||||||
# [bot]
|
|
||||||
# nickname = "MaiBot"
|
|
||||||
# qq_account = "123456"
|
|
||||||
#
|
|
||||||
# [model.default]
|
|
||||||
# model_name = "gpt-3.5-turbo"
|
|
||||||
# temperature = 0.7
|
|
||||||
|
|
||||||
# API调用:
|
|
||||||
bot_name = config_api.get_global_config("bot.nickname")
|
|
||||||
model_name = config_api.get_global_config("model.default.model_name")
|
|
||||||
temperature = config_api.get_global_config("model.default.temperature")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **只读访问**:配置API只提供读取功能,插件不能修改全局配置
|
1. **只读访问**:配置API只提供读取功能,插件不能修改全局配置
|
||||||
2. **异步函数**:用户信息相关的函数是异步的,需要使用`await`
|
2. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值
|
||||||
3. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值
|
3. **安全性**:插件通过此API访问配置是安全和隔离的
|
||||||
4. **安全性**:插件通过此API访问配置是安全和隔离的
|
4. **性能**:频繁访问的配置建议在插件初始化时获取并缓存
|
||||||
5. **性能**:频繁访问的配置建议在插件初始化时获取并缓存
|
|
||||||
@@ -6,72 +6,51 @@
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import database_api
|
from src.plugin_system.apis import database_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import database_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. 通用数据库查询
|
### 1. 通用数据库操作
|
||||||
|
|
||||||
#### `db_query(model_class, query_type="get", filters=None, data=None, limit=None, order_by=None, single_result=False)`
|
|
||||||
执行数据库查询操作的通用接口
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `model_class`:Peewee模型类,如ActionRecords、Messages等
|
|
||||||
- `query_type`:查询类型,可选值: "get", "create", "update", "delete", "count"
|
|
||||||
- `filters`:过滤条件字典,键为字段名,值为要匹配的值
|
|
||||||
- `data`:用于创建或更新的数据字典
|
|
||||||
- `limit`:限制结果数量
|
|
||||||
- `order_by`:排序字段列表,使用字段名,前缀'-'表示降序
|
|
||||||
- `single_result`:是否只返回单个结果
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
根据查询类型返回不同的结果:
|
|
||||||
- "get":返回查询结果列表或单个结果
|
|
||||||
- "create":返回创建的记录
|
|
||||||
- "update":返回受影响的行数
|
|
||||||
- "delete":返回受影响的行数
|
|
||||||
- "count":返回记录数量
|
|
||||||
|
|
||||||
### 2. 便捷查询函数
|
|
||||||
|
|
||||||
#### `db_save(model_class, data, key_field=None, key_value=None)`
|
|
||||||
保存数据到数据库(创建或更新)
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `model_class`:Peewee模型类
|
|
||||||
- `data`:要保存的数据字典
|
|
||||||
- `key_field`:用于查找现有记录的字段名
|
|
||||||
- `key_value`:用于查找现有记录的字段值
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Dict[str, Any]`:保存后的记录数据,失败时返回None
|
|
||||||
|
|
||||||
#### `db_get(model_class, filters=None, order_by=None, limit=None)`
|
|
||||||
简化的查询函数
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `model_class`:Peewee模型类
|
|
||||||
- `filters`:过滤条件字典
|
|
||||||
- `order_by`:排序字段
|
|
||||||
- `limit`:限制结果数量
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Union[List[Dict], Dict, None]`:查询结果
|
|
||||||
|
|
||||||
### 3. 专用函数
|
|
||||||
|
|
||||||
#### `store_action_info(...)`
|
|
||||||
存储动作信息的专用函数
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 1. 基本查询操作
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import database_api
|
async def db_query(
|
||||||
from src.common.database.database_model import Messages, ActionRecords
|
model_class: Type[Model],
|
||||||
|
data: Optional[Dict[str, Any]] = None,
|
||||||
|
query_type: Optional[str] = "get",
|
||||||
|
filters: Optional[Dict[str, Any]] = None,
|
||||||
|
limit: Optional[int] = None,
|
||||||
|
order_by: Optional[List[str]] = None,
|
||||||
|
single_result: Optional[bool] = False,
|
||||||
|
) -> Union[List[Dict[str, Any]], Dict[str, Any], None]:
|
||||||
|
```
|
||||||
|
执行数据库查询操作的通用接口。
|
||||||
|
|
||||||
# 查询最近10条消息
|
**Args:**
|
||||||
|
- `model_class`: Peewee模型类。
|
||||||
|
- Peewee模型类可以在`src.common.database.database_model`模块中找到,如`ActionRecords`、`Messages`等。
|
||||||
|
- `data`: 用于创建或更新的数据
|
||||||
|
- `query_type`: 查询类型
|
||||||
|
- 可选值: `get`, `create`, `update`, `delete`, `count`。
|
||||||
|
- `filters`: 过滤条件字典,键为字段名,值为要匹配的值。
|
||||||
|
- `limit`: 限制结果数量。
|
||||||
|
- `order_by`: 排序字段列表,使用字段名,前缀'-'表示降序。
|
||||||
|
- 排序字段,前缀`-`表示降序,例如`-time`表示按时间字段(即`time`字段)降序
|
||||||
|
- `single_result`: 是否只返回单个结果。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- 根据查询类型返回不同的结果:
|
||||||
|
- `get`: 返回查询结果列表或单个结果。(如果 `single_result=True`)
|
||||||
|
- `create`: 返回创建的记录。
|
||||||
|
- `update`: 返回受影响的行数。
|
||||||
|
- `delete`: 返回受影响的行数。
|
||||||
|
- `count`: 返回记录数量。
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
|
||||||
|
1. 查询最近10条消息
|
||||||
|
```python
|
||||||
messages = await database_api.db_query(
|
messages = await database_api.db_query(
|
||||||
Messages,
|
Messages,
|
||||||
query_type="get",
|
query_type="get",
|
||||||
@@ -79,180 +58,159 @@ messages = await database_api.db_query(
|
|||||||
limit=10,
|
limit=10,
|
||||||
order_by=["-time"]
|
order_by=["-time"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# 查询单条记录
|
|
||||||
message = await database_api.db_query(
|
|
||||||
Messages,
|
|
||||||
query_type="get",
|
|
||||||
filters={"message_id": "msg_123"},
|
|
||||||
single_result=True
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
2. 创建一条记录
|
||||||
### 2. 创建记录
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 创建新的动作记录
|
|
||||||
new_record = await database_api.db_query(
|
new_record = await database_api.db_query(
|
||||||
ActionRecords,
|
ActionRecords,
|
||||||
|
data={"action_id": "123", "time": time.time(), "action_name": "TestAction"},
|
||||||
query_type="create",
|
query_type="create",
|
||||||
data={
|
|
||||||
"action_id": "action_123",
|
|
||||||
"time": time.time(),
|
|
||||||
"action_name": "TestAction",
|
|
||||||
"action_done": True
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"创建了记录: {new_record['id']}")
|
|
||||||
```
|
```
|
||||||
|
3. 更新记录
|
||||||
### 3. 更新记录
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 更新动作状态
|
|
||||||
updated_count = await database_api.db_query(
|
updated_count = await database_api.db_query(
|
||||||
ActionRecords,
|
ActionRecords,
|
||||||
|
data={"action_done": True},
|
||||||
query_type="update",
|
query_type="update",
|
||||||
filters={"action_id": "action_123"},
|
filters={"action_id": "123"},
|
||||||
data={"action_done": True, "completion_time": time.time()}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"更新了 {updated_count} 条记录")
|
|
||||||
```
|
```
|
||||||
|
4. 删除记录
|
||||||
### 4. 删除记录
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 删除过期记录
|
|
||||||
deleted_count = await database_api.db_query(
|
deleted_count = await database_api.db_query(
|
||||||
ActionRecords,
|
ActionRecords,
|
||||||
query_type="delete",
|
query_type="delete",
|
||||||
filters={"time__lt": time.time() - 86400} # 删除24小时前的记录
|
filters={"action_id": "123"}
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"删除了 {deleted_count} 条过期记录")
|
|
||||||
```
|
```
|
||||||
|
5. 计数
|
||||||
### 5. 统计查询
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 统计消息数量
|
count = await database_api.db_query(
|
||||||
message_count = await database_api.db_query(
|
|
||||||
Messages,
|
Messages,
|
||||||
query_type="count",
|
query_type="count",
|
||||||
filters={"chat_id": chat_stream.stream_id}
|
filters={"chat_id": chat_stream.stream_id}
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"该聊天有 {message_count} 条消息")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. 使用便捷函数
|
### 2. 数据库保存
|
||||||
|
```python
|
||||||
|
async def db_save(
|
||||||
|
model_class: Type[Model], data: Dict[str, Any], key_field: Optional[str] = None, key_value: Optional[Any] = None
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
保存数据到数据库(创建或更新)
|
||||||
|
|
||||||
|
如果提供了key_field和key_value,会先尝试查找匹配的记录进行更新;
|
||||||
|
|
||||||
|
如果没有找到匹配记录,或未提供key_field和key_value,则创建新记录。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `model_class`: Peewee模型类。
|
||||||
|
- `data`: 要保存的数据字典。
|
||||||
|
- `key_field`: 用于查找现有记录的字段名,例如"action_id"。
|
||||||
|
- `key_value`: 用于查找现有记录的字段值。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Optional[Dict[str, Any]]`: 保存后的记录数据,失败时返回None。
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
创建或更新一条记录
|
||||||
```python
|
```python
|
||||||
# 使用db_save进行创建或更新
|
|
||||||
record = await database_api.db_save(
|
record = await database_api.db_save(
|
||||||
ActionRecords,
|
ActionRecords,
|
||||||
{
|
{
|
||||||
"action_id": "action_123",
|
"action_id": "123",
|
||||||
"time": time.time(),
|
"time": time.time(),
|
||||||
"action_name": "TestAction",
|
"action_name": "TestAction",
|
||||||
"action_done": True
|
"action_done": True
|
||||||
},
|
},
|
||||||
key_field="action_id",
|
key_field="action_id",
|
||||||
key_value="action_123"
|
key_value="123"
|
||||||
)
|
)
|
||||||
|
```
|
||||||
|
|
||||||
# 使用db_get进行简单查询
|
### 3. 数据库获取
|
||||||
recent_messages = await database_api.db_get(
|
```python
|
||||||
|
async def db_get(
|
||||||
|
model_class: Type[Model],
|
||||||
|
filters: Optional[Dict[str, Any]] = None,
|
||||||
|
limit: Optional[int] = None,
|
||||||
|
order_by: Optional[str] = None,
|
||||||
|
single_result: Optional[bool] = False,
|
||||||
|
) -> Union[List[Dict[str, Any]], Dict[str, Any], None]:
|
||||||
|
```
|
||||||
|
|
||||||
|
从数据库获取记录
|
||||||
|
|
||||||
|
这是db_query方法的简化版本,专注于数据检索操作。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `model_class`: Peewee模型类。
|
||||||
|
- `filters`: 过滤条件字典,键为字段名,值为要匹配的值。
|
||||||
|
- `limit`: 限制结果数量。
|
||||||
|
- `order_by`: 排序字段,使用字段名,前缀'-'表示降序。
|
||||||
|
- `single_result`: 是否只返回单个结果,如果为True,则返回单个记录字典或None;否则返回记录字典列表或空列表
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Union[List[Dict], Dict, None]`: 查询结果列表或单个结果(如果`single_result=True`),失败时返回None。
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
1. 获取单个记录
|
||||||
|
```python
|
||||||
|
record = await database_api.db_get(
|
||||||
|
ActionRecords,
|
||||||
|
filters={"action_id": "123"},
|
||||||
|
limit=1
|
||||||
|
)
|
||||||
|
```
|
||||||
|
2. 获取最近10条记录
|
||||||
|
```python
|
||||||
|
records = await database_api.db_get(
|
||||||
Messages,
|
Messages,
|
||||||
filters={"chat_id": chat_stream.stream_id},
|
filters={"chat_id": chat_stream.stream_id},
|
||||||
|
limit=10,
|
||||||
order_by="-time",
|
order_by="-time",
|
||||||
limit=5
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 高级用法
|
### 4. 动作信息存储
|
||||||
|
|
||||||
### 复杂查询示例
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 查询特定用户在特定时间段的消息
|
async def store_action_info(
|
||||||
user_messages = await database_api.db_query(
|
chat_stream=None,
|
||||||
Messages,
|
action_build_into_prompt: bool = False,
|
||||||
query_type="get",
|
action_prompt_display: str = "",
|
||||||
filters={
|
action_done: bool = True,
|
||||||
"user_id": "123456",
|
thinking_id: str = "",
|
||||||
"time__gte": start_time, # 大于等于开始时间
|
action_data: Optional[dict] = None,
|
||||||
"time__lt": end_time # 小于结束时间
|
action_name: str = "",
|
||||||
},
|
) -> Optional[Dict[str, Any]]:
|
||||||
order_by=["-time"],
|
```
|
||||||
limit=50
|
存储动作信息到数据库,是一种针对 Action 的 `db_save()` 的封装函数。
|
||||||
|
|
||||||
|
将Action执行的相关信息保存到ActionRecords表中,用于后续的记忆和上下文构建。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `chat_stream`: 聊天流对象,包含聊天ID等信息。
|
||||||
|
- `action_build_into_prompt`: 是否将动作信息构建到提示中。
|
||||||
|
- `action_prompt_display`: 动作提示的显示文本。
|
||||||
|
- `action_done`: 动作是否完成。
|
||||||
|
- `thinking_id`: 思考过程的ID。
|
||||||
|
- `action_data`: 动作的数据字典。
|
||||||
|
- `action_name`: 动作的名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Optional[Dict[str, Any]]`: 存储后的记录数据,失败时返回None。
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```python
|
||||||
|
record = await database_api.store_action_info(
|
||||||
|
chat_stream=chat_stream,
|
||||||
|
action_build_into_prompt=True,
|
||||||
|
action_prompt_display="执行了回复动作",
|
||||||
|
action_done=True,
|
||||||
|
thinking_id="thinking_123",
|
||||||
|
action_data={"content": "Hello"},
|
||||||
|
action_name="reply_action"
|
||||||
)
|
)
|
||||||
|
```
|
||||||
# 批量处理
|
|
||||||
for message in user_messages:
|
|
||||||
print(f"消息内容: {message['plain_text']}")
|
|
||||||
print(f"发送时间: {message['time']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 插件中的数据持久化
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.base import BasePlugin
|
|
||||||
from src.plugin_system.apis import database_api
|
|
||||||
|
|
||||||
class DataPlugin(BasePlugin):
|
|
||||||
async def handle_action(self, action_data, chat_stream):
|
|
||||||
# 保存插件数据
|
|
||||||
plugin_data = {
|
|
||||||
"plugin_name": self.plugin_name,
|
|
||||||
"chat_id": chat_stream.stream_id,
|
|
||||||
"data": json.dumps(action_data),
|
|
||||||
"created_time": time.time()
|
|
||||||
}
|
|
||||||
|
|
||||||
# 使用自定义表模型(需要先定义)
|
|
||||||
record = await database_api.db_save(
|
|
||||||
PluginData, # 假设的插件数据模型
|
|
||||||
plugin_data,
|
|
||||||
key_field="plugin_name",
|
|
||||||
key_value=self.plugin_name
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"success": True, "record_id": record["id"]}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据模型
|
|
||||||
|
|
||||||
### 常用模型类
|
|
||||||
系统提供了以下常用的数据模型:
|
|
||||||
|
|
||||||
- `Messages`:消息记录
|
|
||||||
- `ActionRecords`:动作记录
|
|
||||||
- `UserInfo`:用户信息
|
|
||||||
- `GroupInfo`:群组信息
|
|
||||||
|
|
||||||
### 字段说明
|
|
||||||
|
|
||||||
#### Messages模型主要字段
|
|
||||||
- `message_id`:消息ID
|
|
||||||
- `chat_id`:聊天ID
|
|
||||||
- `user_id`:用户ID
|
|
||||||
- `plain_text`:纯文本内容
|
|
||||||
- `time`:时间戳
|
|
||||||
|
|
||||||
#### ActionRecords模型主要字段
|
|
||||||
- `action_id`:动作ID
|
|
||||||
- `action_name`:动作名称
|
|
||||||
- `action_done`:是否完成
|
|
||||||
- `time`:创建时间
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **异步操作**:所有数据库API都是异步的,必须使用`await`
|
|
||||||
2. **错误处理**:函数内置错误处理,失败时返回None或空列表
|
|
||||||
3. **数据类型**:返回的都是字典格式的数据,不是模型对象
|
|
||||||
4. **性能考虑**:使用`limit`参数避免查询大量数据
|
|
||||||
5. **过滤条件**:支持简单的等值过滤,复杂查询需要使用原生Peewee语法
|
|
||||||
6. **事务**:如需事务支持,建议直接使用Peewee的事务功能
|
|
||||||
@@ -6,11 +6,13 @@
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import emoji_api
|
from src.plugin_system.apis import emoji_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import emoji_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🆕 **二步走识别优化**
|
## 二步走识别优化
|
||||||
|
|
||||||
从最新版本开始,表情包识别系统采用了**二步走识别 + 智能缓存**的优化方案:
|
从新版本开始,表情包识别系统采用了**二步走识别 + 智能缓存**的优化方案:
|
||||||
|
|
||||||
### **收到表情包时的识别流程**
|
### **收到表情包时的识别流程**
|
||||||
1. **第一步**:VLM视觉分析 - 生成详细描述
|
1. **第一步**:VLM视觉分析 - 生成详细描述
|
||||||
@@ -30,217 +32,84 @@ from src.plugin_system.apis import emoji_api
|
|||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. 表情包获取
|
### 1. 表情包获取
|
||||||
|
```python
|
||||||
#### `get_by_description(description: str) -> Optional[Tuple[str, str, str]]`
|
async def get_by_description(description: str) -> Optional[Tuple[str, str, str]]:
|
||||||
|
```
|
||||||
根据场景描述选择表情包
|
根据场景描述选择表情包
|
||||||
|
|
||||||
**参数:**
|
**Args:**
|
||||||
- `description`:场景描述文本,例如"开心的大笑"、"轻微的讽刺"、"表示无奈和沮丧"等
|
- `description`:表情包的描述文本,例如"开心"、"难过"、"愤怒"等
|
||||||
|
|
||||||
**返回:**
|
**Returns:**
|
||||||
- `Optional[Tuple[str, str, str]]`:(base64编码, 表情包描述, 匹配的场景) 或 None
|
- `Optional[Tuple[str, str, str]]`:一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到匹配的表情包则返回None
|
||||||
|
|
||||||
**示例:**
|
#### 示例
|
||||||
```python
|
```python
|
||||||
emoji_result = await emoji_api.get_by_description("开心的大笑")
|
emoji_result = await emoji_api.get_by_description("大笑")
|
||||||
if emoji_result:
|
if emoji_result:
|
||||||
emoji_base64, description, matched_scene = emoji_result
|
emoji_base64, description, matched_scene = emoji_result
|
||||||
print(f"获取到表情包: {description}, 场景: {matched_scene}")
|
print(f"获取到表情包: {description}, 场景: {matched_scene}")
|
||||||
# 可以将emoji_base64用于发送表情包
|
# 可以将emoji_base64用于发送表情包
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_random() -> Optional[Tuple[str, str, str]]`
|
### 2. 随机获取表情包
|
||||||
随机获取表情包
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Optional[Tuple[str, str, str]]`:(base64编码, 表情包描述, 随机场景) 或 None
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
random_emoji = await emoji_api.get_random()
|
async def get_random(count: Optional[int] = 1) -> List[Tuple[str, str, str]]:
|
||||||
if random_emoji:
|
|
||||||
emoji_base64, description, scene = random_emoji
|
|
||||||
print(f"随机表情包: {description}")
|
|
||||||
```
|
```
|
||||||
|
随机获取指定数量的表情包
|
||||||
|
|
||||||
#### `get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]`
|
**Args:**
|
||||||
根据场景关键词获取表情包
|
- `count`:要获取的表情包数量,默认为1
|
||||||
|
|
||||||
**参数:**
|
**Returns:**
|
||||||
- `emotion`:场景关键词,如"大笑"、"讽刺"、"无奈"等
|
- `List[Tuple[str, str, str]]`:一个包含多个表情包的列表,每个元素是一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到或出错则返回空列表
|
||||||
|
|
||||||
**返回:**
|
### 3. 根据情感获取表情包
|
||||||
- `Optional[Tuple[str, str, str]]`:(base64编码, 表情包描述, 匹配的场景) 或 None
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
emoji_result = await emoji_api.get_by_emotion("讽刺")
|
async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]:
|
||||||
if emoji_result:
|
|
||||||
emoji_base64, description, scene = emoji_result
|
|
||||||
# 发送讽刺表情包
|
|
||||||
```
|
```
|
||||||
|
根据情感标签获取表情包
|
||||||
|
|
||||||
### 2. 表情包信息查询
|
**Args:**
|
||||||
|
- `emotion`:情感标签,例如"开心"、"悲伤"、"愤怒"等
|
||||||
|
|
||||||
#### `get_count() -> int`
|
**Returns:**
|
||||||
获取表情包数量
|
- `Optional[Tuple[str, str, str]]`:一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到则返回None
|
||||||
|
|
||||||
**返回:**
|
### 4. 获取表情包数量
|
||||||
- `int`:当前可用的表情包数量
|
```python
|
||||||
|
def get_count() -> int:
|
||||||
|
```
|
||||||
|
获取当前可用表情包的数量
|
||||||
|
|
||||||
#### `get_info() -> dict`
|
### 5. 获取表情包系统信息
|
||||||
获取表情包系统信息
|
```python
|
||||||
|
def get_info() -> Dict[str, Any]:
|
||||||
|
```
|
||||||
|
获取表情包系统的基本信息
|
||||||
|
|
||||||
**返回:**
|
**Returns:**
|
||||||
- `dict`:包含表情包数量、最大数量等信息
|
- `Dict[str, Any]`:包含表情包数量、描述等信息的字典,包含以下键:
|
||||||
|
- `current_count`:当前表情包数量
|
||||||
|
- `max_count`:最大表情包数量
|
||||||
|
- `available_emojis`:当前可用的表情包数量
|
||||||
|
|
||||||
**返回字典包含:**
|
### 6. 获取所有可用的情感标签
|
||||||
- `current_count`:当前表情包数量
|
```python
|
||||||
- `max_count`:最大表情包数量
|
def get_emotions() -> List[str]:
|
||||||
- `available_emojis`:可用表情包数量
|
```
|
||||||
|
获取所有可用的情感标签 **(已经去重)**
|
||||||
|
|
||||||
#### `get_emotions() -> list`
|
### 7. 获取所有表情包描述
|
||||||
获取所有可用的场景关键词
|
```python
|
||||||
|
def get_descriptions() -> List[str]:
|
||||||
**返回:**
|
```
|
||||||
- `list`:所有表情包的场景关键词列表(去重)
|
|
||||||
|
|
||||||
#### `get_descriptions() -> list`
|
|
||||||
获取所有表情包的描述列表
|
获取所有表情包的描述列表
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `list`:所有表情包的描述文本列表
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 1. 智能表情包选择
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import emoji_api
|
|
||||||
|
|
||||||
async def send_emotion_response(message_text: str, chat_stream):
|
|
||||||
"""根据消息内容智能选择表情包回复"""
|
|
||||||
|
|
||||||
# 分析消息场景
|
|
||||||
if "哈哈" in message_text or "好笑" in message_text:
|
|
||||||
emoji_result = await emoji_api.get_by_description("开心的大笑")
|
|
||||||
elif "无语" in message_text or "算了" in message_text:
|
|
||||||
emoji_result = await emoji_api.get_by_description("表示无奈和沮丧")
|
|
||||||
elif "呵呵" in message_text or "是吗" in message_text:
|
|
||||||
emoji_result = await emoji_api.get_by_description("轻微的讽刺")
|
|
||||||
elif "生气" in message_text or "愤怒" in message_text:
|
|
||||||
emoji_result = await emoji_api.get_by_description("愤怒和不满")
|
|
||||||
else:
|
|
||||||
# 随机选择一个表情包
|
|
||||||
emoji_result = await emoji_api.get_random()
|
|
||||||
|
|
||||||
if emoji_result:
|
|
||||||
emoji_base64, description, scene = emoji_result
|
|
||||||
# 使用send_api发送表情包
|
|
||||||
from src.plugin_system.apis import send_api
|
|
||||||
success = await send_api.emoji_to_group(emoji_base64, chat_stream.group_info.group_id)
|
|
||||||
return success
|
|
||||||
|
|
||||||
return False
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 表情包管理功能
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def show_emoji_stats():
|
|
||||||
"""显示表情包统计信息"""
|
|
||||||
|
|
||||||
# 获取基本信息
|
|
||||||
count = emoji_api.get_count()
|
|
||||||
info = emoji_api.get_info()
|
|
||||||
scenes = emoji_api.get_emotions() # 实际返回的是场景关键词
|
|
||||||
|
|
||||||
stats = f"""
|
|
||||||
📊 表情包统计信息:
|
|
||||||
- 总数量: {count}
|
|
||||||
- 可用数量: {info['available_emojis']}
|
|
||||||
- 最大容量: {info['max_count']}
|
|
||||||
- 支持场景: {len(scenes)}种
|
|
||||||
|
|
||||||
🎭 支持的场景关键词: {', '.join(scenes[:10])}{'...' if len(scenes) > 10 else ''}
|
|
||||||
"""
|
|
||||||
|
|
||||||
return stats
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 表情包测试功能
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def test_emoji_system():
|
|
||||||
"""测试表情包系统的各种功能"""
|
|
||||||
|
|
||||||
print("=== 表情包系统测试 ===")
|
|
||||||
|
|
||||||
# 测试场景描述查找
|
|
||||||
test_descriptions = ["开心的大笑", "轻微的讽刺", "表示无奈和沮丧", "愤怒和不满"]
|
|
||||||
for desc in test_descriptions:
|
|
||||||
result = await emoji_api.get_by_description(desc)
|
|
||||||
if result:
|
|
||||||
_, description, scene = result
|
|
||||||
print(f"✅ 场景'{desc}' -> {description} ({scene})")
|
|
||||||
else:
|
|
||||||
print(f"❌ 场景'{desc}' -> 未找到")
|
|
||||||
|
|
||||||
# 测试关键词查找
|
|
||||||
scenes = emoji_api.get_emotions()
|
|
||||||
if scenes:
|
|
||||||
test_scene = scenes[0]
|
|
||||||
result = await emoji_api.get_by_emotion(test_scene)
|
|
||||||
if result:
|
|
||||||
print(f"✅ 关键词'{test_scene}' -> 找到匹配表情包")
|
|
||||||
|
|
||||||
# 测试随机获取
|
|
||||||
random_result = await emoji_api.get_random()
|
|
||||||
if random_result:
|
|
||||||
print("✅ 随机获取 -> 成功")
|
|
||||||
|
|
||||||
print(f"📊 系统信息: {emoji_api.get_info()}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 在Action中使用表情包
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.base import BaseAction
|
|
||||||
|
|
||||||
class EmojiAction(BaseAction):
|
|
||||||
async def execute(self, action_data, chat_stream):
|
|
||||||
# 从action_data获取场景描述或关键词
|
|
||||||
scene_keyword = action_data.get("scene", "")
|
|
||||||
scene_description = action_data.get("description", "")
|
|
||||||
|
|
||||||
emoji_result = None
|
|
||||||
|
|
||||||
# 优先使用具体的场景描述
|
|
||||||
if scene_description:
|
|
||||||
emoji_result = await emoji_api.get_by_description(scene_description)
|
|
||||||
# 其次使用场景关键词
|
|
||||||
elif scene_keyword:
|
|
||||||
emoji_result = await emoji_api.get_by_emotion(scene_keyword)
|
|
||||||
# 最后随机选择
|
|
||||||
else:
|
|
||||||
emoji_result = await emoji_api.get_random()
|
|
||||||
|
|
||||||
if emoji_result:
|
|
||||||
emoji_base64, description, scene = emoji_result
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"emoji_base64": emoji_base64,
|
|
||||||
"description": description,
|
|
||||||
"scene": scene
|
|
||||||
}
|
|
||||||
|
|
||||||
return {"success": False, "message": "未找到合适的表情包"}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 场景描述说明
|
## 场景描述说明
|
||||||
|
|
||||||
### 常用场景描述
|
### 常用场景描述
|
||||||
表情包系统支持多种具体的场景描述,常见的包括:
|
表情包系统支持多种具体的场景描述,举例如下:
|
||||||
|
|
||||||
- **开心类场景**:开心的大笑、满意的微笑、兴奋的手舞足蹈
|
- **开心类场景**:开心的大笑、满意的微笑、兴奋的手舞足蹈
|
||||||
- **无奈类场景**:表示无奈和沮丧、轻微的讽刺、无语的摇头
|
- **无奈类场景**:表示无奈和沮丧、轻微的讽刺、无语的摇头
|
||||||
@@ -248,8 +117,8 @@ class EmojiAction(BaseAction):
|
|||||||
- **惊讶类场景**:震惊的表情、意外的发现、困惑的思考
|
- **惊讶类场景**:震惊的表情、意外的发现、困惑的思考
|
||||||
- **可爱类场景**:卖萌的表情、撒娇的动作、害羞的样子
|
- **可爱类场景**:卖萌的表情、撒娇的动作、害羞的样子
|
||||||
|
|
||||||
### 场景关键词示例
|
### 情感关键词示例
|
||||||
系统支持的场景关键词包括:
|
系统支持的情感关键词举例如下:
|
||||||
- 大笑、微笑、兴奋、手舞足蹈
|
- 大笑、微笑、兴奋、手舞足蹈
|
||||||
- 无奈、沮丧、讽刺、无语、摇头
|
- 无奈、沮丧、讽刺、无语、摇头
|
||||||
- 愤怒、不满、生气、瞪视、抓狂
|
- 愤怒、不满、生气、瞪视、抓狂
|
||||||
@@ -263,9 +132,9 @@ class EmojiAction(BaseAction):
|
|||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **异步函数**:获取表情包的函数都是异步的,需要使用 `await`
|
1. **异步函数**:部分函数是异步的,需要使用 `await`
|
||||||
2. **返回格式**:表情包以base64编码返回,可直接用于发送
|
2. **返回格式**:表情包以base64编码返回,可直接用于发送
|
||||||
3. **错误处理**:所有函数都有错误处理,失败时返回None或默认值
|
3. **错误处理**:所有函数都有错误处理,失败时返回None,空列表或默认值
|
||||||
4. **使用统计**:系统会记录表情包的使用次数
|
4. **使用统计**:系统会记录表情包的使用次数
|
||||||
5. **文件依赖**:表情包依赖于本地文件,确保表情包文件存在
|
5. **文件依赖**:表情包依赖于本地文件,确保表情包文件存在
|
||||||
6. **编码格式**:返回的是base64编码的图片数据,可直接用于网络传输
|
6. **编码格式**:返回的是base64编码的图片数据,可直接用于网络传输
|
||||||
|
|||||||
@@ -6,241 +6,150 @@
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import generator_api
|
from src.plugin_system.apis import generator_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import generator_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. 回复器获取
|
### 1. 回复器获取
|
||||||
|
```python
|
||||||
#### `get_replyer(chat_stream=None, platform=None, chat_id=None, is_group=True)`
|
def get_replyer(
|
||||||
|
chat_stream: Optional[ChatStream] = None,
|
||||||
|
chat_id: Optional[str] = None,
|
||||||
|
model_configs: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
request_type: str = "replyer",
|
||||||
|
) -> Optional[DefaultReplyer]:
|
||||||
|
```
|
||||||
获取回复器对象
|
获取回复器对象
|
||||||
|
|
||||||
**参数:**
|
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||||
- `chat_stream`:聊天流对象(优先)
|
|
||||||
- `platform`:平台名称,如"qq"
|
|
||||||
- `chat_id`:聊天ID(群ID或用户ID)
|
|
||||||
- `is_group`:是否为群聊
|
|
||||||
|
|
||||||
**返回:**
|
使用 ReplyerManager 来管理实例,避免重复创建。
|
||||||
- `DefaultReplyer`:回复器对象,如果获取失败则返回None
|
|
||||||
|
|
||||||
**示例:**
|
**Args:**
|
||||||
|
- `chat_stream`: 聊天流对象
|
||||||
|
- `chat_id`: 聊天ID(实际上就是`stream_id`)
|
||||||
|
- `model_configs`: 模型配置
|
||||||
|
- `request_type`: 请求类型,用于记录LLM使用情况,可以不写
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `DefaultReplyer`: 回复器对象,如果获取失败则返回None
|
||||||
|
|
||||||
|
#### 示例
|
||||||
```python
|
```python
|
||||||
# 使用聊天流获取回复器
|
# 使用聊天流获取回复器
|
||||||
replyer = generator_api.get_replyer(chat_stream=chat_stream)
|
replyer = generator_api.get_replyer(chat_stream=chat_stream)
|
||||||
|
|
||||||
# 使用平台和ID获取回复器
|
# 使用平台和ID获取回复器
|
||||||
replyer = generator_api.get_replyer(
|
replyer = generator_api.get_replyer(chat_id="123456789")
|
||||||
platform="qq",
|
|
||||||
chat_id="123456789",
|
|
||||||
is_group=True
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 回复生成
|
### 2. 回复生成
|
||||||
|
```python
|
||||||
#### `generate_reply(chat_stream=None, action_data=None, platform=None, chat_id=None, is_group=True)`
|
async def generate_reply(
|
||||||
|
chat_stream: Optional[ChatStream] = None,
|
||||||
|
chat_id: Optional[str] = None,
|
||||||
|
action_data: Optional[Dict[str, Any]] = None,
|
||||||
|
reply_to: str = "",
|
||||||
|
extra_info: str = "",
|
||||||
|
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||||||
|
enable_tool: bool = False,
|
||||||
|
enable_splitter: bool = True,
|
||||||
|
enable_chinese_typo: bool = True,
|
||||||
|
return_prompt: bool = False,
|
||||||
|
model_configs: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
request_type: str = "",
|
||||||
|
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
|
||||||
|
```
|
||||||
生成回复
|
生成回复
|
||||||
|
|
||||||
**参数:**
|
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||||
- `chat_stream`:聊天流对象(优先)
|
|
||||||
- `action_data`:动作数据
|
|
||||||
- `platform`:平台名称(备用)
|
|
||||||
- `chat_id`:聊天ID(备用)
|
|
||||||
- `is_group`:是否为群聊(备用)
|
|
||||||
|
|
||||||
**返回:**
|
**Args:**
|
||||||
- `Tuple[bool, List[Tuple[str, Any]]]`:(是否成功, 回复集合)
|
- `chat_stream`: 聊天流对象
|
||||||
|
- `chat_id`: 聊天ID(实际上就是`stream_id`)
|
||||||
|
- `action_data`: 动作数据(向下兼容,包含`reply_to`和`extra_info`)
|
||||||
|
- `reply_to`: 回复目标,格式为 `{发送者的person_name:消息内容}`
|
||||||
|
- `extra_info`: 附加信息
|
||||||
|
- `available_actions`: 可用动作字典,格式为 `{"action_name": ActionInfo}`
|
||||||
|
- `enable_tool`: 是否启用工具
|
||||||
|
- `enable_splitter`: 是否启用分割器
|
||||||
|
- `enable_chinese_typo`: 是否启用中文错别字
|
||||||
|
- `return_prompt`: 是否返回提示词
|
||||||
|
- `model_configs`: 模型配置,可选
|
||||||
|
- `request_type`: 请求类型,用于记录LLM使用情况
|
||||||
|
|
||||||
**示例:**
|
**Returns:**
|
||||||
|
- `Tuple[bool, List[Tuple[str, Any]], Optional[str]]`: (是否成功, 回复集合, 提示词)
|
||||||
|
|
||||||
|
#### 示例
|
||||||
```python
|
```python
|
||||||
success, reply_set = await generator_api.generate_reply(
|
success, reply_set, prompt = await generator_api.generate_reply(
|
||||||
chat_stream=chat_stream,
|
chat_stream=chat_stream,
|
||||||
action_data={"message": "你好", "intent": "greeting"}
|
action_data=action_data,
|
||||||
|
reply_to="麦麦:你好",
|
||||||
|
available_actions=action_info,
|
||||||
|
enable_tool=True,
|
||||||
|
return_prompt=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
for reply_type, reply_content in reply_set:
|
for reply_type, reply_content in reply_set:
|
||||||
print(f"回复类型: {reply_type}, 内容: {reply_content}")
|
print(f"回复类型: {reply_type}, 内容: {reply_content}")
|
||||||
|
if prompt:
|
||||||
|
print(f"使用的提示词: {prompt}")
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `rewrite_reply(chat_stream=None, reply_data=None, platform=None, chat_id=None, is_group=True)`
|
### 3. 回复重写
|
||||||
重写回复
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `chat_stream`:聊天流对象(优先)
|
|
||||||
- `reply_data`:回复数据
|
|
||||||
- `platform`:平台名称(备用)
|
|
||||||
- `chat_id`:聊天ID(备用)
|
|
||||||
- `is_group`:是否为群聊(备用)
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Tuple[bool, List[Tuple[str, Any]]]`:(是否成功, 回复集合)
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
success, reply_set = await generator_api.rewrite_reply(
|
async def rewrite_reply(
|
||||||
|
chat_stream: Optional[ChatStream] = None,
|
||||||
|
reply_data: Optional[Dict[str, Any]] = None,
|
||||||
|
chat_id: Optional[str] = None,
|
||||||
|
enable_splitter: bool = True,
|
||||||
|
enable_chinese_typo: bool = True,
|
||||||
|
model_configs: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
raw_reply: str = "",
|
||||||
|
reason: str = "",
|
||||||
|
reply_to: str = "",
|
||||||
|
return_prompt: bool = False,
|
||||||
|
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
|
||||||
|
```
|
||||||
|
重写回复,使用新的内容替换旧的回复内容。
|
||||||
|
|
||||||
|
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `chat_stream`: 聊天流对象
|
||||||
|
- `reply_data`: 回复数据,包含`raw_reply`, `reason`和`reply_to`,**(向下兼容备用,当其他参数缺失时从此获取)**
|
||||||
|
- `chat_id`: 聊天ID(实际上就是`stream_id`)
|
||||||
|
- `enable_splitter`: 是否启用分割器
|
||||||
|
- `enable_chinese_typo`: 是否启用中文错别字
|
||||||
|
- `model_configs`: 模型配置,可选
|
||||||
|
- `raw_reply`: 原始回复内容
|
||||||
|
- `reason`: 重写原因
|
||||||
|
- `reply_to`: 回复目标,格式为 `{发送者的person_name:消息内容}`
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Tuple[bool, List[Tuple[str, Any]], Optional[str]]`: (是否成功, 回复集合, 提示词)
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
```python
|
||||||
|
success, reply_set, prompt = await generator_api.rewrite_reply(
|
||||||
chat_stream=chat_stream,
|
chat_stream=chat_stream,
|
||||||
reply_data={"original_text": "原始回复", "style": "more_friendly"}
|
raw_reply="原始回复内容",
|
||||||
|
reason="重写原因",
|
||||||
|
reply_to="麦麦:你好",
|
||||||
|
return_prompt=True
|
||||||
)
|
)
|
||||||
|
if success:
|
||||||
|
for reply_type, reply_content in reply_set:
|
||||||
|
print(f"回复类型: {reply_type}, 内容: {reply_content}")
|
||||||
|
if prompt:
|
||||||
|
print(f"使用的提示词: {prompt}")
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用示例
|
## 回复集合`reply_set`格式
|
||||||
|
|
||||||
### 1. 基础回复生成
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import generator_api
|
|
||||||
|
|
||||||
async def generate_greeting_reply(chat_stream, user_name):
|
|
||||||
"""生成问候回复"""
|
|
||||||
|
|
||||||
action_data = {
|
|
||||||
"intent": "greeting",
|
|
||||||
"user_name": user_name,
|
|
||||||
"context": "morning_greeting"
|
|
||||||
}
|
|
||||||
|
|
||||||
success, reply_set = await generator_api.generate_reply(
|
|
||||||
chat_stream=chat_stream,
|
|
||||||
action_data=action_data
|
|
||||||
)
|
|
||||||
|
|
||||||
if success and reply_set:
|
|
||||||
# 获取第一个回复
|
|
||||||
reply_type, reply_content = reply_set[0]
|
|
||||||
return reply_content
|
|
||||||
|
|
||||||
return "你好!" # 默认回复
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 在Action中使用回复生成器
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.base import BaseAction
|
|
||||||
|
|
||||||
class ChatAction(BaseAction):
|
|
||||||
async def execute(self, action_data, chat_stream):
|
|
||||||
# 准备回复数据
|
|
||||||
reply_context = {
|
|
||||||
"message_type": "response",
|
|
||||||
"user_input": action_data.get("user_message", ""),
|
|
||||||
"intent": action_data.get("intent", ""),
|
|
||||||
"entities": action_data.get("entities", {}),
|
|
||||||
"context": self.get_conversation_context(chat_stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
# 生成回复
|
|
||||||
success, reply_set = await generator_api.generate_reply(
|
|
||||||
chat_stream=chat_stream,
|
|
||||||
action_data=reply_context
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"replies": reply_set,
|
|
||||||
"generated_count": len(reply_set)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": False,
|
|
||||||
"error": "回复生成失败",
|
|
||||||
"fallback_reply": "抱歉,我现在无法理解您的消息。"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 多样化回复生成
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def generate_diverse_replies(chat_stream, topic, count=3):
|
|
||||||
"""生成多个不同风格的回复"""
|
|
||||||
|
|
||||||
styles = ["formal", "casual", "humorous"]
|
|
||||||
all_replies = []
|
|
||||||
|
|
||||||
for i, style in enumerate(styles[:count]):
|
|
||||||
action_data = {
|
|
||||||
"topic": topic,
|
|
||||||
"style": style,
|
|
||||||
"variation": i
|
|
||||||
}
|
|
||||||
|
|
||||||
success, reply_set = await generator_api.generate_reply(
|
|
||||||
chat_stream=chat_stream,
|
|
||||||
action_data=action_data
|
|
||||||
)
|
|
||||||
|
|
||||||
if success and reply_set:
|
|
||||||
all_replies.extend(reply_set)
|
|
||||||
|
|
||||||
return all_replies
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 回复重写功能
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def improve_reply(chat_stream, original_reply, improvement_type="more_friendly"):
|
|
||||||
"""改进原始回复"""
|
|
||||||
|
|
||||||
reply_data = {
|
|
||||||
"original_text": original_reply,
|
|
||||||
"improvement_type": improvement_type,
|
|
||||||
"target_audience": "young_users",
|
|
||||||
"tone": "positive"
|
|
||||||
}
|
|
||||||
|
|
||||||
success, improved_replies = await generator_api.rewrite_reply(
|
|
||||||
chat_stream=chat_stream,
|
|
||||||
reply_data=reply_data
|
|
||||||
)
|
|
||||||
|
|
||||||
if success and improved_replies:
|
|
||||||
# 返回改进后的第一个回复
|
|
||||||
_, improved_content = improved_replies[0]
|
|
||||||
return improved_content
|
|
||||||
|
|
||||||
return original_reply # 如果改进失败,返回原始回复
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 条件回复生成
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def conditional_reply_generation(chat_stream, user_message, user_emotion):
|
|
||||||
"""根据用户情感生成条件回复"""
|
|
||||||
|
|
||||||
# 根据情感调整回复策略
|
|
||||||
if user_emotion == "sad":
|
|
||||||
action_data = {
|
|
||||||
"intent": "comfort",
|
|
||||||
"tone": "empathetic",
|
|
||||||
"style": "supportive"
|
|
||||||
}
|
|
||||||
elif user_emotion == "angry":
|
|
||||||
action_data = {
|
|
||||||
"intent": "calm",
|
|
||||||
"tone": "peaceful",
|
|
||||||
"style": "understanding"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
action_data = {
|
|
||||||
"intent": "respond",
|
|
||||||
"tone": "neutral",
|
|
||||||
"style": "helpful"
|
|
||||||
}
|
|
||||||
|
|
||||||
action_data["user_message"] = user_message
|
|
||||||
action_data["user_emotion"] = user_emotion
|
|
||||||
|
|
||||||
success, reply_set = await generator_api.generate_reply(
|
|
||||||
chat_stream=chat_stream,
|
|
||||||
action_data=action_data
|
|
||||||
)
|
|
||||||
|
|
||||||
return reply_set if success else []
|
|
||||||
```
|
|
||||||
|
|
||||||
## 回复集合格式
|
|
||||||
|
|
||||||
### 回复类型
|
### 回复类型
|
||||||
生成的回复集合包含多种类型的回复:
|
生成的回复集合包含多种类型的回复:
|
||||||
@@ -260,82 +169,32 @@ reply_set = [
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 高级用法
|
### 4. 自定义提示词回复
|
||||||
|
|
||||||
### 1. 自定义回复器配置
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def generate_with_custom_config(chat_stream, action_data):
|
async def generate_response_custom(
|
||||||
"""使用自定义配置生成回复"""
|
chat_stream: Optional[ChatStream] = None,
|
||||||
|
chat_id: Optional[str] = None,
|
||||||
# 获取回复器
|
model_configs: Optional[List[Dict[str, Any]]] = None,
|
||||||
replyer = generator_api.get_replyer(chat_stream=chat_stream)
|
prompt: str = "",
|
||||||
|
) -> Optional[str]:
|
||||||
if replyer:
|
|
||||||
# 可以访问回复器的内部方法
|
|
||||||
success, reply_set = await replyer.generate_reply_with_context(
|
|
||||||
reply_data=action_data,
|
|
||||||
# 可以传递额外的配置参数
|
|
||||||
)
|
|
||||||
return success, reply_set
|
|
||||||
|
|
||||||
return False, []
|
|
||||||
```
|
```
|
||||||
|
生成自定义提示词回复
|
||||||
|
|
||||||
### 2. 回复质量评估
|
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||||
|
|
||||||
```python
|
**Args:**
|
||||||
async def generate_and_evaluate_replies(chat_stream, action_data):
|
- `chat_stream`: 聊天流对象
|
||||||
"""生成回复并评估质量"""
|
- `chat_id`: 聊天ID(备用)
|
||||||
|
- `model_configs`: 模型配置列表
|
||||||
success, reply_set = await generator_api.generate_reply(
|
- `prompt`: 自定义提示词
|
||||||
chat_stream=chat_stream,
|
|
||||||
action_data=action_data
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
evaluated_replies = []
|
|
||||||
for reply_type, reply_content in reply_set:
|
|
||||||
# 简单的质量评估
|
|
||||||
quality_score = evaluate_reply_quality(reply_content)
|
|
||||||
evaluated_replies.append({
|
|
||||||
"type": reply_type,
|
|
||||||
"content": reply_content,
|
|
||||||
"quality": quality_score
|
|
||||||
})
|
|
||||||
|
|
||||||
# 按质量排序
|
|
||||||
evaluated_replies.sort(key=lambda x: x["quality"], reverse=True)
|
|
||||||
return evaluated_replies
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def evaluate_reply_quality(reply_content):
|
**Returns:**
|
||||||
"""简单的回复质量评估"""
|
- `Optional[str]`: 生成的自定义回复内容,如果生成失败则返回None
|
||||||
if not reply_content:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
score = 50 # 基础分
|
|
||||||
|
|
||||||
# 长度适中加分
|
|
||||||
if 5 <= len(reply_content) <= 100:
|
|
||||||
score += 20
|
|
||||||
|
|
||||||
# 包含积极词汇加分
|
|
||||||
positive_words = ["好", "棒", "不错", "感谢", "开心"]
|
|
||||||
for word in positive_words:
|
|
||||||
if word in reply_content:
|
|
||||||
score += 10
|
|
||||||
break
|
|
||||||
|
|
||||||
return min(score, 100)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **异步操作**:所有生成函数都是异步的,必须使用`await`
|
1. **异步操作**:部分函数是异步的,须使用`await`
|
||||||
2. **错误处理**:函数内置错误处理,失败时返回False和空列表
|
2. **聊天流依赖**:需要有效的聊天流对象才能正常工作
|
||||||
3. **聊天流依赖**:需要有效的聊天流对象才能正常工作
|
3. **性能考虑**:回复生成可能需要一些时间,特别是使用LLM时
|
||||||
4. **性能考虑**:回复生成可能需要一些时间,特别是使用LLM时
|
4. **回复格式**:返回的回复集合是元组列表,包含类型和内容
|
||||||
5. **回复格式**:返回的回复集合是元组列表,包含类型和内容
|
5. **上下文感知**:生成器会考虑聊天上下文和历史消息,除非你用的是自定义提示词。
|
||||||
6. **上下文感知**:生成器会考虑聊天上下文和历史消息
|
|
||||||
@@ -6,239 +6,34 @@ LLM API模块提供与大语言模型交互的功能,让插件能够使用系
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import llm_api
|
from src.plugin_system.apis import llm_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import llm_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. 模型管理
|
### 1. 查询可用模型
|
||||||
|
|
||||||
#### `get_available_models() -> Dict[str, Any]`
|
|
||||||
获取所有可用的模型配置
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Dict[str, Any]`:模型配置字典,key为模型名称,value为模型配置
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
models = llm_api.get_available_models()
|
def get_available_models() -> Dict[str, Any]:
|
||||||
for model_name, model_config in models.items():
|
|
||||||
print(f"模型: {model_name}")
|
|
||||||
print(f"配置: {model_config}")
|
|
||||||
```
|
```
|
||||||
|
获取所有可用的模型配置。
|
||||||
|
|
||||||
### 2. 内容生成
|
**Return:**
|
||||||
|
- `Dict[str, Any]`:模型配置字典,key为模型名称,value为模型配置。
|
||||||
|
|
||||||
#### `generate_with_model(prompt, model_config, request_type="plugin.generate", **kwargs)`
|
### 2. 使用模型生成内容
|
||||||
使用指定模型生成内容
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `prompt`:提示词
|
|
||||||
- `model_config`:模型配置(从 get_available_models 获取)
|
|
||||||
- `request_type`:请求类型标识
|
|
||||||
- `**kwargs`:其他模型特定参数,如temperature、max_tokens等
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Tuple[bool, str, str, str]`:(是否成功, 生成的内容, 推理过程, 模型名称)
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
models = llm_api.get_available_models()
|
async def generate_with_model(
|
||||||
default_model = models.get("default")
|
prompt: str, model_config: Dict[str, Any], request_type: str = "plugin.generate", **kwargs
|
||||||
|
) -> Tuple[bool, str]:
|
||||||
if default_model:
|
|
||||||
success, response, reasoning, model_name = await llm_api.generate_with_model(
|
|
||||||
prompt="请写一首关于春天的诗",
|
|
||||||
model_config=default_model,
|
|
||||||
temperature=0.7,
|
|
||||||
max_tokens=200
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
print(f"生成内容: {response}")
|
|
||||||
print(f"使用模型: {model_name}")
|
|
||||||
```
|
```
|
||||||
|
使用指定模型生成内容。
|
||||||
|
|
||||||
## 使用示例
|
**Args:**
|
||||||
|
- `prompt`:提示词。
|
||||||
|
- `model_config`:模型配置(从 `get_available_models` 获取)。
|
||||||
|
- `request_type`:请求类型标识,默认为 `"plugin.generate"`。
|
||||||
|
- `**kwargs`:其他模型特定参数,如 `temperature`、`max_tokens` 等。
|
||||||
|
|
||||||
### 1. 基础文本生成
|
**Return:**
|
||||||
|
- `Tuple[bool, str]`:返回一个元组,第一个元素表示是否成功,第二个元素为生成的内容或错误信息。
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import llm_api
|
|
||||||
|
|
||||||
async def generate_story(topic: str):
|
|
||||||
"""生成故事"""
|
|
||||||
models = llm_api.get_available_models()
|
|
||||||
model = models.get("default")
|
|
||||||
|
|
||||||
if not model:
|
|
||||||
return "未找到可用模型"
|
|
||||||
|
|
||||||
prompt = f"请写一个关于{topic}的短故事,大约100字左右。"
|
|
||||||
|
|
||||||
success, story, reasoning, model_name = await llm_api.generate_with_model(
|
|
||||||
prompt=prompt,
|
|
||||||
model_config=model,
|
|
||||||
request_type="story.generate",
|
|
||||||
temperature=0.8,
|
|
||||||
max_tokens=150
|
|
||||||
)
|
|
||||||
|
|
||||||
return story if success else "故事生成失败"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 在Action中使用LLM
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.base import BaseAction
|
|
||||||
|
|
||||||
class LLMAction(BaseAction):
|
|
||||||
async def execute(self, action_data, chat_stream):
|
|
||||||
# 获取用户输入
|
|
||||||
user_input = action_data.get("user_message", "")
|
|
||||||
intent = action_data.get("intent", "chat")
|
|
||||||
|
|
||||||
# 获取模型配置
|
|
||||||
models = llm_api.get_available_models()
|
|
||||||
model = models.get("default")
|
|
||||||
|
|
||||||
if not model:
|
|
||||||
return {"success": False, "error": "未配置LLM模型"}
|
|
||||||
|
|
||||||
# 构建提示词
|
|
||||||
prompt = self.build_prompt(user_input, intent)
|
|
||||||
|
|
||||||
# 生成回复
|
|
||||||
success, response, reasoning, model_name = await llm_api.generate_with_model(
|
|
||||||
prompt=prompt,
|
|
||||||
model_config=model,
|
|
||||||
request_type=f"plugin.{self.plugin_name}",
|
|
||||||
temperature=0.7
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"response": response,
|
|
||||||
"model_used": model_name,
|
|
||||||
"reasoning": reasoning
|
|
||||||
}
|
|
||||||
|
|
||||||
return {"success": False, "error": response}
|
|
||||||
|
|
||||||
def build_prompt(self, user_input: str, intent: str) -> str:
|
|
||||||
"""构建提示词"""
|
|
||||||
base_prompt = "你是一个友善的AI助手。"
|
|
||||||
|
|
||||||
if intent == "question":
|
|
||||||
return f"{base_prompt}\n\n用户问题:{user_input}\n\n请提供准确、有用的回答:"
|
|
||||||
elif intent == "chat":
|
|
||||||
return f"{base_prompt}\n\n用户说:{user_input}\n\n请进行自然的对话:"
|
|
||||||
else:
|
|
||||||
return f"{base_prompt}\n\n用户输入:{user_input}\n\n请回复:"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 多模型对比
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def compare_models(prompt: str):
|
|
||||||
"""使用多个模型生成内容并对比"""
|
|
||||||
models = llm_api.get_available_models()
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
for model_name, model_config in models.items():
|
|
||||||
success, response, reasoning, actual_model = await llm_api.generate_with_model(
|
|
||||||
prompt=prompt,
|
|
||||||
model_config=model_config,
|
|
||||||
request_type="comparison.test"
|
|
||||||
)
|
|
||||||
|
|
||||||
results[model_name] = {
|
|
||||||
"success": success,
|
|
||||||
"response": response,
|
|
||||||
"model": actual_model,
|
|
||||||
"reasoning": reasoning
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 智能对话插件
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ChatbotPlugin(BasePlugin):
|
|
||||||
async def handle_action(self, action_data, chat_stream):
|
|
||||||
user_message = action_data.get("message", "")
|
|
||||||
|
|
||||||
# 获取历史对话上下文
|
|
||||||
context = self.get_conversation_context(chat_stream)
|
|
||||||
|
|
||||||
# 构建对话提示词
|
|
||||||
prompt = self.build_conversation_prompt(user_message, context)
|
|
||||||
|
|
||||||
# 获取模型配置
|
|
||||||
models = llm_api.get_available_models()
|
|
||||||
chat_model = models.get("chat", models.get("default"))
|
|
||||||
|
|
||||||
if not chat_model:
|
|
||||||
return {"success": False, "message": "聊天模型未配置"}
|
|
||||||
|
|
||||||
# 生成回复
|
|
||||||
success, response, reasoning, model_name = await llm_api.generate_with_model(
|
|
||||||
prompt=prompt,
|
|
||||||
model_config=chat_model,
|
|
||||||
request_type="chat.conversation",
|
|
||||||
temperature=0.8,
|
|
||||||
max_tokens=500
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
# 保存对话历史
|
|
||||||
self.save_conversation(chat_stream, user_message, response)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"reply": response,
|
|
||||||
"model": model_name
|
|
||||||
}
|
|
||||||
|
|
||||||
return {"success": False, "message": "回复生成失败"}
|
|
||||||
|
|
||||||
def build_conversation_prompt(self, user_message: str, context: list) -> str:
|
|
||||||
"""构建对话提示词"""
|
|
||||||
prompt = "你是一个有趣、友善的聊天机器人。请自然地回复用户的消息。\n\n"
|
|
||||||
|
|
||||||
# 添加历史对话
|
|
||||||
if context:
|
|
||||||
prompt += "对话历史:\n"
|
|
||||||
for msg in context[-5:]: # 只保留最近5条
|
|
||||||
prompt += f"用户: {msg['user']}\n机器人: {msg['bot']}\n"
|
|
||||||
prompt += "\n"
|
|
||||||
|
|
||||||
prompt += f"用户: {user_message}\n机器人: "
|
|
||||||
return prompt
|
|
||||||
```
|
|
||||||
|
|
||||||
## 模型配置说明
|
|
||||||
|
|
||||||
### 常用模型类型
|
|
||||||
- `default`:默认模型
|
|
||||||
- `chat`:聊天专用模型
|
|
||||||
- `creative`:创意生成模型
|
|
||||||
- `code`:代码生成模型
|
|
||||||
|
|
||||||
### 配置参数
|
|
||||||
LLM模型支持的常用参数:
|
|
||||||
- `temperature`:控制输出随机性(0.0-1.0)
|
|
||||||
- `max_tokens`:最大生成长度
|
|
||||||
- `top_p`:核采样参数
|
|
||||||
- `frequency_penalty`:频率惩罚
|
|
||||||
- `presence_penalty`:存在惩罚
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **异步操作**:LLM生成是异步的,必须使用`await`
|
|
||||||
2. **错误处理**:生成失败时返回False和错误信息
|
|
||||||
3. **配置依赖**:需要正确配置模型才能使用
|
|
||||||
4. **请求类型**:建议为不同用途设置不同的request_type
|
|
||||||
5. **性能考虑**:LLM调用可能较慢,考虑超时和缓存
|
|
||||||
6. **成本控制**:注意控制max_tokens以控制成本
|
|
||||||
29
docs/plugins/api/logging-api.md
Normal file
29
docs/plugins/api/logging-api.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Logging API
|
||||||
|
|
||||||
|
Logging API模块提供了获取本体logger的功能,允许插件记录日志信息。
|
||||||
|
|
||||||
|
## 导入方式
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system.apis import get_logger
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import get_logger
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
### 1. 获取本体logger
|
||||||
|
```python
|
||||||
|
def get_logger(name: str) -> structlog.stdlib.BoundLogger:
|
||||||
|
```
|
||||||
|
获取本体logger实例。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `name` (str): 日志记录器的名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- 一个logger实例,有以下方法:
|
||||||
|
- `debug`
|
||||||
|
- `info`
|
||||||
|
- `warning`
|
||||||
|
- `error`
|
||||||
|
- `critical`
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
# 消息API
|
# 消息API
|
||||||
|
|
||||||
> 消息API提供了强大的消息查询、计数和格式化功能,让你轻松处理聊天消息数据。
|
消息API提供了强大的消息查询、计数和格式化功能,让你轻松处理聊天消息数据。
|
||||||
|
|
||||||
## 导入方式
|
## 导入方式
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import message_api
|
from src.plugin_system.apis import message_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import message_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 功能概述
|
## 功能概述
|
||||||
@@ -15,297 +17,356 @@ from src.plugin_system.apis import message_api
|
|||||||
- **消息计数** - 统计新消息数量
|
- **消息计数** - 统计新消息数量
|
||||||
- **消息格式化** - 将消息转换为可读格式
|
- **消息格式化** - 将消息转换为可读格式
|
||||||
|
|
||||||
---
|
## 主要功能
|
||||||
|
|
||||||
## 消息查询API
|
### 1. 按照事件查询消息
|
||||||
|
```python
|
||||||
|
def get_messages_by_time(
|
||||||
|
start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
获取指定时间范围内的消息。
|
||||||
|
|
||||||
### 按时间查询消息
|
**Args:**
|
||||||
|
|
||||||
#### `get_messages_by_time(start_time, end_time, limit=0, limit_mode="latest")`
|
|
||||||
|
|
||||||
获取指定时间范围内的消息
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `start_time` (float): 开始时间戳
|
- `start_time` (float): 开始时间戳
|
||||||
- `end_time` (float): 结束时间戳
|
- `end_time` (float): 结束时间戳
|
||||||
- `limit` (int): 限制返回消息数量,0为不限制
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||||
|
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||||
|
|
||||||
**返回:** `List[Dict[str, Any]]` - 消息列表
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
**示例:**
|
消息列表中包含的键与`Messages`类的属性一致。(位于`src.common.database.database_model`)
|
||||||
|
|
||||||
|
### 2. 获取指定聊天中指定时间范围内的信息
|
||||||
```python
|
```python
|
||||||
import time
|
def get_messages_by_time_in_chat(
|
||||||
|
chat_id: str,
|
||||||
# 获取最近24小时的消息
|
start_time: float,
|
||||||
now = time.time()
|
end_time: float,
|
||||||
yesterday = now - 24 * 3600
|
limit: int = 0,
|
||||||
messages = message_api.get_messages_by_time(yesterday, now, limit=50)
|
limit_mode: str = "latest",
|
||||||
|
filter_mai: bool = False,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
```
|
```
|
||||||
|
获取指定聊天中指定时间范围内的消息。
|
||||||
|
|
||||||
### 按聊天查询消息
|
**Args:**
|
||||||
|
|
||||||
#### `get_messages_by_time_in_chat(chat_id, start_time, end_time, limit=0, limit_mode="latest")`
|
|
||||||
|
|
||||||
获取指定聊天中指定时间范围内的消息
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `chat_id` (str): 聊天ID
|
|
||||||
- 其他参数同上
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
# 获取某个群聊最近的100条消息
|
|
||||||
messages = message_api.get_messages_by_time_in_chat(
|
|
||||||
chat_id="123456789",
|
|
||||||
start_time=yesterday,
|
|
||||||
end_time=now,
|
|
||||||
limit=100
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `get_messages_by_time_in_chat_inclusive(chat_id, start_time, end_time, limit=0, limit_mode="latest")`
|
|
||||||
|
|
||||||
获取指定聊天中指定时间范围内的消息(包含边界时间点)
|
|
||||||
|
|
||||||
与 `get_messages_by_time_in_chat` 类似,但包含边界时间戳的消息。
|
|
||||||
|
|
||||||
#### `get_recent_messages(chat_id, hours=24.0, limit=100, limit_mode="latest")`
|
|
||||||
|
|
||||||
获取指定聊天中最近一段时间的消息(便捷方法)
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `chat_id` (str): 聊天ID
|
|
||||||
- `hours` (float): 最近多少小时,默认24小时
|
|
||||||
- `limit` (int): 限制返回消息数量,默认100条
|
|
||||||
- `limit_mode` (str): 限制模式
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
# 获取最近6小时的消息
|
|
||||||
recent_messages = message_api.get_recent_messages(
|
|
||||||
chat_id="123456789",
|
|
||||||
hours=6.0,
|
|
||||||
limit=50
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 按用户查询消息
|
|
||||||
|
|
||||||
#### `get_messages_by_time_in_chat_for_users(chat_id, start_time, end_time, person_ids, limit=0, limit_mode="latest")`
|
|
||||||
|
|
||||||
获取指定聊天中指定用户在指定时间范围内的消息
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `chat_id` (str): 聊天ID
|
- `chat_id` (str): 聊天ID
|
||||||
- `start_time` (float): 开始时间戳
|
- `start_time` (float): 开始时间戳
|
||||||
- `end_time` (float): 结束时间戳
|
- `end_time` (float): 结束时间戳
|
||||||
- `person_ids` (list): 用户ID列表
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
- `limit` (int): 限制返回消息数量
|
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||||
- `limit_mode` (str): 限制模式
|
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||||
|
|
||||||
**示例:**
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
|
|
||||||
|
### 3. 获取指定聊天中指定时间范围内的信息(包含边界)
|
||||||
```python
|
```python
|
||||||
# 获取特定用户的消息
|
def get_messages_by_time_in_chat_inclusive(
|
||||||
user_messages = message_api.get_messages_by_time_in_chat_for_users(
|
chat_id: str,
|
||||||
chat_id="123456789",
|
start_time: float,
|
||||||
start_time=yesterday,
|
end_time: float,
|
||||||
end_time=now,
|
limit: int = 0,
|
||||||
person_ids=["user1", "user2"]
|
limit_mode: str = "latest",
|
||||||
)
|
filter_mai: bool = False,
|
||||||
|
filter_command: bool = False,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
```
|
```
|
||||||
|
获取指定聊天中指定时间范围内的消息(包含边界)。
|
||||||
|
|
||||||
#### `get_messages_by_time_for_users(start_time, end_time, person_ids, limit=0, limit_mode="latest")`
|
**Args:**
|
||||||
|
- `chat_id` (str): 聊天ID
|
||||||
|
- `start_time` (float): 开始时间戳(包含)
|
||||||
|
- `end_time` (float): 结束时间戳(包含)
|
||||||
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
|
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||||
|
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||||
|
- `filter_command` (bool): 是否过滤命令消息,默认False
|
||||||
|
|
||||||
获取指定用户在所有聊天中指定时间范围内的消息
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
### 其他查询方法
|
|
||||||
|
|
||||||
#### `get_random_chat_messages(start_time, end_time, limit=0, limit_mode="latest")`
|
### 4. 获取指定聊天中指定用户在指定时间范围内的消息
|
||||||
|
```python
|
||||||
|
def get_messages_by_time_in_chat_for_users(
|
||||||
|
chat_id: str,
|
||||||
|
start_time: float,
|
||||||
|
end_time: float,
|
||||||
|
person_ids: List[str],
|
||||||
|
limit: int = 0,
|
||||||
|
limit_mode: str = "latest",
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
获取指定聊天中指定用户在指定时间范围内的消息。
|
||||||
|
|
||||||
随机选择一个聊天,返回该聊天在指定时间范围内的消息
|
**Args:**
|
||||||
|
|
||||||
#### `get_messages_before_time(timestamp, limit=0)`
|
|
||||||
|
|
||||||
获取指定时间戳之前的消息
|
|
||||||
|
|
||||||
#### `get_messages_before_time_in_chat(chat_id, timestamp, limit=0)`
|
|
||||||
|
|
||||||
获取指定聊天中指定时间戳之前的消息
|
|
||||||
|
|
||||||
#### `get_messages_before_time_for_users(timestamp, person_ids, limit=0)`
|
|
||||||
|
|
||||||
获取指定用户在指定时间戳之前的消息
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 消息计数API
|
|
||||||
|
|
||||||
### `count_new_messages(chat_id, start_time=0.0, end_time=None)`
|
|
||||||
|
|
||||||
计算指定聊天中从开始时间到结束时间的新消息数量
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `chat_id` (str): 聊天ID
|
- `chat_id` (str): 聊天ID
|
||||||
- `start_time` (float): 开始时间戳
|
- `start_time` (float): 开始时间戳
|
||||||
- `end_time` (float): 结束时间戳,如果为None则使用当前时间
|
- `end_time` (float): 结束时间戳
|
||||||
|
- `person_ids` (List[str]): 用户ID列表
|
||||||
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
|
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||||
|
|
||||||
**返回:** `int` - 新消息数量
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
### 5. 随机选择一个聊天,返回该聊天在指定时间范围内的消息
|
||||||
```python
|
```python
|
||||||
# 计算最近1小时的新消息数
|
def get_random_chat_messages(
|
||||||
import time
|
start_time: float,
|
||||||
now = time.time()
|
end_time: float,
|
||||||
hour_ago = now - 3600
|
limit: int = 0,
|
||||||
new_count = message_api.count_new_messages("123456789", hour_ago, now)
|
limit_mode: str = "latest",
|
||||||
print(f"最近1小时有{new_count}条新消息")
|
filter_mai: bool = False,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
```
|
```
|
||||||
|
随机选择一个聊天,返回该聊天在指定时间范围内的消息。
|
||||||
|
|
||||||
### `count_new_messages_for_users(chat_id, start_time, end_time, person_ids)`
|
**Args:**
|
||||||
|
- `start_time` (float): 开始时间戳
|
||||||
|
- `end_time` (float): 结束时间戳
|
||||||
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
|
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||||
|
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||||
|
|
||||||
计算指定聊天中指定用户从开始时间到结束时间的新消息数量
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 消息格式化API
|
### 6. 获取指定用户在所有聊天中指定时间范围内的消息
|
||||||
|
```python
|
||||||
|
def get_messages_by_time_for_users(
|
||||||
|
start_time: float,
|
||||||
|
end_time: float,
|
||||||
|
person_ids: List[str],
|
||||||
|
limit: int = 0,
|
||||||
|
limit_mode: str = "latest",
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
获取指定用户在所有聊天中指定时间范围内的消息。
|
||||||
|
|
||||||
### `build_readable_messages_to_str(messages, **options)`
|
**Args:**
|
||||||
|
- `start_time` (float): 开始时间戳
|
||||||
|
- `end_time` (float): 结束时间戳
|
||||||
|
- `person_ids` (List[str]): 用户ID列表
|
||||||
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
|
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||||
|
|
||||||
将消息列表构建成可读的字符串
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
### 7. 获取指定时间戳之前的消息
|
||||||
|
```python
|
||||||
|
def get_messages_before_time(
|
||||||
|
timestamp: float,
|
||||||
|
limit: int = 0,
|
||||||
|
filter_mai: bool = False,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
获取指定时间戳之前的消息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `timestamp` (float): 时间戳
|
||||||
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
|
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
|
|
||||||
|
### 8. 获取指定聊天中指定时间戳之前的消息
|
||||||
|
```python
|
||||||
|
def get_messages_before_time_in_chat(
|
||||||
|
chat_id: str,
|
||||||
|
timestamp: float,
|
||||||
|
limit: int = 0,
|
||||||
|
filter_mai: bool = False,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
获取指定聊天中指定时间戳之前的消息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `chat_id` (str): 聊天ID
|
||||||
|
- `timestamp` (float): 时间戳
|
||||||
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
|
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
|
|
||||||
|
### 9. 获取指定用户在指定时间戳之前的消息
|
||||||
|
```python
|
||||||
|
def get_messages_before_time_for_users(
|
||||||
|
timestamp: float,
|
||||||
|
person_ids: List[str],
|
||||||
|
limit: int = 0,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
获取指定用户在指定时间戳之前的消息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `timestamp` (float): 时间戳
|
||||||
|
- `person_ids` (List[str]): 用户ID列表
|
||||||
|
- `limit` (int): 限制返回消息数量,0为不限制
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
|
|
||||||
|
### 10. 获取指定聊天中最近一段时间的消息
|
||||||
|
```python
|
||||||
|
def get_recent_messages(
|
||||||
|
chat_id: str,
|
||||||
|
hours: float = 24.0,
|
||||||
|
limit: int = 100,
|
||||||
|
limit_mode: str = "latest",
|
||||||
|
filter_mai: bool = False,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
```
|
||||||
|
获取指定聊天中最近一段时间的消息。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `chat_id` (str): 聊天ID
|
||||||
|
- `hours` (float): 最近多少小时,默认24小时
|
||||||
|
- `limit` (int): 限制返回消息数量,默认100条
|
||||||
|
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||||
|
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 消息列表
|
||||||
|
|
||||||
|
|
||||||
|
### 11. 计算指定聊天中从开始时间到结束时间的新消息数量
|
||||||
|
```python
|
||||||
|
def count_new_messages(
|
||||||
|
chat_id: str,
|
||||||
|
start_time: float = 0.0,
|
||||||
|
end_time: Optional[float] = None,
|
||||||
|
) -> int:
|
||||||
|
```
|
||||||
|
计算指定聊天中从开始时间到结束时间的新消息数量。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `chat_id` (str): 聊天ID
|
||||||
|
- `start_time` (float): 开始时间戳
|
||||||
|
- `end_time` (Optional[float]): 结束时间戳,如果为None则使用当前时间
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `int` - 新消息数量
|
||||||
|
|
||||||
|
|
||||||
|
### 12. 计算指定聊天中指定用户从开始时间到结束时间的新消息数量
|
||||||
|
```python
|
||||||
|
def count_new_messages_for_users(
|
||||||
|
chat_id: str,
|
||||||
|
start_time: float,
|
||||||
|
end_time: float,
|
||||||
|
person_ids: List[str],
|
||||||
|
) -> int:
|
||||||
|
```
|
||||||
|
计算指定聊天中指定用户从开始时间到结束时间的新消息数量。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `chat_id` (str): 聊天ID
|
||||||
|
- `start_time` (float): 开始时间戳
|
||||||
|
- `end_time` (float): 结束时间戳
|
||||||
|
- `person_ids` (List[str]): 用户ID列表
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `int` - 新消息数量
|
||||||
|
|
||||||
|
|
||||||
|
### 13. 将消息列表构建成可读的字符串
|
||||||
|
```python
|
||||||
|
def build_readable_messages_to_str(
|
||||||
|
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:
|
||||||
|
```
|
||||||
|
将消息列表构建成可读的字符串。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
- `messages` (List[Dict[str, Any]]): 消息列表
|
- `messages` (List[Dict[str, Any]]): 消息列表
|
||||||
- `replace_bot_name` (bool): 是否将机器人的名称替换为"你",默认True
|
- `replace_bot_name` (bool): 是否将机器人的名称替换为"你"
|
||||||
- `merge_messages` (bool): 是否合并连续消息,默认False
|
- `merge_messages` (bool): 是否合并连续消息
|
||||||
- `timestamp_mode` (str): 时间戳显示模式,`"relative"`或`"absolute"`,默认`"relative"`
|
- `timestamp_mode` (str): 时间戳显示模式,`"relative"`或`"absolute"`
|
||||||
- `read_mark` (float): 已读标记时间戳,用于分割已读和未读消息,默认0.0
|
- `read_mark` (float): 已读标记时间戳,用于分割已读和未读消息
|
||||||
- `truncate` (bool): 是否截断长消息,默认False
|
- `truncate` (bool): 是否截断长消息
|
||||||
- `show_actions` (bool): 是否显示动作记录,默认False
|
- `show_actions` (bool): 是否显示动作记录
|
||||||
|
|
||||||
**返回:** `str` - 格式化后的可读字符串
|
**Returns:**
|
||||||
|
- `str` - 格式化后的可读字符串
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
### 14. 将消息列表构建成可读的字符串,并返回详细信息
|
||||||
```python
|
```python
|
||||||
# 获取消息并格式化为可读文本
|
async def build_readable_messages_with_details(
|
||||||
messages = message_api.get_recent_messages("123456789", hours=2)
|
messages: List[Dict[str, Any]],
|
||||||
readable_text = message_api.build_readable_messages_to_str(
|
replace_bot_name: bool = True,
|
||||||
messages,
|
merge_messages: bool = False,
|
||||||
replace_bot_name=True,
|
timestamp_mode: str = "relative",
|
||||||
merge_messages=True,
|
truncate: bool = False,
|
||||||
timestamp_mode="relative"
|
) -> Tuple[str, List[Tuple[float, str, str]]]:
|
||||||
)
|
|
||||||
print(readable_text)
|
|
||||||
```
|
```
|
||||||
|
将消息列表构建成可读的字符串,并返回详细信息。
|
||||||
|
|
||||||
### `build_readable_messages_with_details(messages, **options)` 异步
|
**Args:**
|
||||||
|
- `messages` (List[Dict[str, Any]]): 消息列表
|
||||||
|
- `replace_bot_name` (bool): 是否将机器人的名称替换为"你"
|
||||||
|
- `merge_messages` (bool): 是否合并连续消息
|
||||||
|
- `timestamp_mode` (str): 时间戳显示模式,`"relative"`或`"absolute"`
|
||||||
|
- `truncate` (bool): 是否截断长消息
|
||||||
|
|
||||||
将消息列表构建成可读的字符串,并返回详细信息
|
**Returns:**
|
||||||
|
- `Tuple[str, List[Tuple[float, str, str]]]` - 格式化后的可读字符串和详细信息元组列表(时间戳, 昵称, 内容)
|
||||||
|
|
||||||
**参数:** 与 `build_readable_messages_to_str` 类似,但不包含 `read_mark` 和 `show_actions`
|
|
||||||
|
|
||||||
**返回:** `Tuple[str, List[Tuple[float, str, str]]]` - 格式化字符串和详细信息元组列表(时间戳, 昵称, 内容)
|
### 15. 从消息列表中提取不重复的用户ID列表
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
```python
|
||||||
# 异步获取详细格式化信息
|
async def get_person_ids_from_messages(
|
||||||
readable_text, details = await message_api.build_readable_messages_with_details(
|
messages: List[Dict[str, Any]],
|
||||||
messages,
|
) -> List[str]:
|
||||||
timestamp_mode="absolute"
|
|
||||||
)
|
|
||||||
|
|
||||||
for timestamp, nickname, content in details:
|
|
||||||
print(f"{timestamp}: {nickname} 说: {content}")
|
|
||||||
```
|
```
|
||||||
|
从消息列表中提取不重复的用户ID列表。
|
||||||
|
|
||||||
### `get_person_ids_from_messages(messages)` 异步
|
**Args:**
|
||||||
|
|
||||||
从消息列表中提取不重复的用户ID列表
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `messages` (List[Dict[str, Any]]): 消息列表
|
- `messages` (List[Dict[str, Any]]): 消息列表
|
||||||
|
|
||||||
**返回:** `List[str]` - 用户ID列表
|
**Returns:**
|
||||||
|
- `List[str]` - 用户ID列表
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
### 16. 从消息列表中移除机器人的消息
|
||||||
```python
|
```python
|
||||||
# 获取参与对话的所有用户ID
|
def filter_mai_messages(
|
||||||
messages = message_api.get_recent_messages("123456789")
|
messages: List[Dict[str, Any]],
|
||||||
person_ids = await message_api.get_person_ids_from_messages(messages)
|
) -> List[Dict[str, Any]]:
|
||||||
print(f"参与对话的用户: {person_ids}")
|
|
||||||
```
|
```
|
||||||
|
从消息列表中移除机器人的消息。
|
||||||
|
|
||||||
---
|
**Args:**
|
||||||
|
- `messages` (List[Dict[str, Any]]): 消息列表,每个元素是消息字典
|
||||||
|
|
||||||
## 完整使用示例
|
**Returns:**
|
||||||
|
- `List[Dict[str, Any]]` - 过滤后的消息列表
|
||||||
### 场景1:统计活跃度
|
|
||||||
|
|
||||||
```python
|
|
||||||
import time
|
|
||||||
from src.plugin_system.apis import message_api
|
|
||||||
|
|
||||||
async def analyze_chat_activity(chat_id: str):
|
|
||||||
"""分析聊天活跃度"""
|
|
||||||
now = time.time()
|
|
||||||
day_ago = now - 24 * 3600
|
|
||||||
|
|
||||||
# 获取最近24小时的消息
|
|
||||||
messages = message_api.get_recent_messages(chat_id, hours=24)
|
|
||||||
|
|
||||||
# 统计消息数量
|
|
||||||
total_count = len(messages)
|
|
||||||
|
|
||||||
# 获取参与用户
|
|
||||||
person_ids = await message_api.get_person_ids_from_messages(messages)
|
|
||||||
|
|
||||||
# 格式化消息内容
|
|
||||||
readable_text = message_api.build_readable_messages_to_str(
|
|
||||||
messages[-10:], # 最后10条消息
|
|
||||||
merge_messages=True,
|
|
||||||
timestamp_mode="relative"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"total_messages": total_count,
|
|
||||||
"active_users": len(person_ids),
|
|
||||||
"recent_chat": readable_text
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 场景2:查看特定用户的历史消息
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_user_history(chat_id: str, user_id: str, days: int = 7):
|
|
||||||
"""获取用户最近N天的消息历史"""
|
|
||||||
now = time.time()
|
|
||||||
start_time = now - days * 24 * 3600
|
|
||||||
|
|
||||||
# 获取特定用户的消息
|
|
||||||
user_messages = message_api.get_messages_by_time_in_chat_for_users(
|
|
||||||
chat_id=chat_id,
|
|
||||||
start_time=start_time,
|
|
||||||
end_time=now,
|
|
||||||
person_ids=[user_id],
|
|
||||||
limit=100
|
|
||||||
)
|
|
||||||
|
|
||||||
# 格式化为可读文本
|
|
||||||
readable_history = message_api.build_readable_messages_to_str(
|
|
||||||
user_messages,
|
|
||||||
replace_bot_name=False,
|
|
||||||
timestamp_mode="absolute"
|
|
||||||
)
|
|
||||||
|
|
||||||
return readable_history
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **时间戳格式**:所有时间参数都使用Unix时间戳(float类型)
|
1. **时间戳格式**:所有时间参数都使用Unix时间戳(float类型)
|
||||||
2. **异步函数**:`build_readable_messages_with_details` 和 `get_person_ids_from_messages` 是异步函数,需要使用 `await`
|
2. **异步函数**:部分函数是异步函数,需要使用 `await`
|
||||||
3. **性能考虑**:查询大量消息时建议设置合理的 `limit` 参数
|
3. **性能考虑**:查询大量消息时建议设置合理的 `limit` 参数
|
||||||
4. **消息格式**:返回的消息是字典格式,包含时间戳、发送者、内容等信息
|
4. **消息格式**:返回的消息是字典格式,包含时间戳、发送者、内容等信息
|
||||||
5. **用户ID**:`person_ids` 参数接受字符串列表,用于筛选特定用户的消息
|
5. **用户ID**:`person_ids` 参数接受字符串列表,用于筛选特定用户的消息
|
||||||
@@ -6,59 +6,65 @@
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import person_api
|
from src.plugin_system.apis import person_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import person_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. Person ID管理
|
### 1. Person ID 获取
|
||||||
|
```python
|
||||||
#### `get_person_id(platform: str, user_id: int) -> str`
|
def get_person_id(platform: str, user_id: int) -> str:
|
||||||
|
```
|
||||||
根据平台和用户ID获取person_id
|
根据平台和用户ID获取person_id
|
||||||
|
|
||||||
**参数:**
|
**Args:**
|
||||||
- `platform`:平台名称,如 "qq", "telegram" 等
|
- `platform`:平台名称,如 "qq", "telegram" 等
|
||||||
- `user_id`:用户ID
|
- `user_id`:用户ID
|
||||||
|
|
||||||
**返回:**
|
**Returns:**
|
||||||
- `str`:唯一的person_id(MD5哈希值)
|
- `str`:唯一的person_id(MD5哈希值)
|
||||||
|
|
||||||
**示例:**
|
#### 示例
|
||||||
```python
|
```python
|
||||||
person_id = person_api.get_person_id("qq", 123456)
|
person_id = person_api.get_person_id("qq", 123456)
|
||||||
print(f"Person ID: {person_id}")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 用户信息查询
|
### 2. 用户信息查询
|
||||||
|
```python
|
||||||
|
async def get_person_value(person_id: str, field_name: str, default: Any = None) -> Any:
|
||||||
|
```
|
||||||
|
查询单个用户信息字段值
|
||||||
|
|
||||||
#### `get_person_value(person_id: str, field_name: str, default: Any = None) -> Any`
|
**Args:**
|
||||||
根据person_id和字段名获取某个值
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `person_id`:用户的唯一标识ID
|
- `person_id`:用户的唯一标识ID
|
||||||
- `field_name`:要获取的字段名,如 "nickname", "impression" 等
|
- `field_name`:要获取的字段名
|
||||||
- `default`:当字段不存在或获取失败时返回的默认值
|
- `default`:字段值不存在时的默认值
|
||||||
|
|
||||||
**返回:**
|
**Returns:**
|
||||||
- `Any`:字段值或默认值
|
- `Any`:字段值或默认值
|
||||||
|
|
||||||
**示例:**
|
#### 示例
|
||||||
```python
|
```python
|
||||||
nickname = await person_api.get_person_value(person_id, "nickname", "未知用户")
|
nickname = await person_api.get_person_value(person_id, "nickname", "未知用户")
|
||||||
impression = await person_api.get_person_value(person_id, "impression")
|
impression = await person_api.get_person_value(person_id, "impression")
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_person_values(person_id: str, field_names: list, default_dict: dict = None) -> dict`
|
### 3. 批量用户信息查询
|
||||||
|
```python
|
||||||
|
async def get_person_values(person_id: str, field_names: list, default_dict: Optional[dict] = None) -> dict:
|
||||||
|
```
|
||||||
批量获取用户信息字段值
|
批量获取用户信息字段值
|
||||||
|
|
||||||
**参数:**
|
**Args:**
|
||||||
- `person_id`:用户的唯一标识ID
|
- `person_id`:用户的唯一标识ID
|
||||||
- `field_names`:要获取的字段名列表
|
- `field_names`:要获取的字段名列表
|
||||||
- `default_dict`:默认值字典,键为字段名,值为默认值
|
- `default_dict`:默认值字典,键为字段名,值为默认值
|
||||||
|
|
||||||
**返回:**
|
**Returns:**
|
||||||
- `dict`:字段名到值的映射字典
|
- `dict`:字段名到值的映射字典
|
||||||
|
|
||||||
**示例:**
|
#### 示例
|
||||||
```python
|
```python
|
||||||
values = await person_api.get_person_values(
|
values = await person_api.get_person_values(
|
||||||
person_id,
|
person_id,
|
||||||
@@ -67,204 +73,31 @@ values = await person_api.get_person_values(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 用户状态查询
|
### 4. 判断用户是否已知
|
||||||
|
```python
|
||||||
#### `is_person_known(platform: str, user_id: int) -> bool`
|
async def is_person_known(platform: str, user_id: int) -> bool:
|
||||||
|
```
|
||||||
判断是否认识某个用户
|
判断是否认识某个用户
|
||||||
|
|
||||||
**参数:**
|
**Args:**
|
||||||
- `platform`:平台名称
|
- `platform`:平台名称
|
||||||
- `user_id`:用户ID
|
- `user_id`:用户ID
|
||||||
|
|
||||||
**返回:**
|
**Returns:**
|
||||||
- `bool`:是否认识该用户
|
- `bool`:是否认识该用户
|
||||||
|
|
||||||
**示例:**
|
### 5. 根据用户名获取Person ID
|
||||||
```python
|
```python
|
||||||
known = await person_api.is_person_known("qq", 123456)
|
def get_person_id_by_name(person_name: str) -> str:
|
||||||
if known:
|
|
||||||
print("这个用户我认识")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 用户名查询
|
|
||||||
|
|
||||||
#### `get_person_id_by_name(person_name: str) -> str`
|
|
||||||
根据用户名获取person_id
|
根据用户名获取person_id
|
||||||
|
|
||||||
**参数:**
|
**Args:**
|
||||||
- `person_name`:用户名
|
- `person_name`:用户名
|
||||||
|
|
||||||
**返回:**
|
**Returns:**
|
||||||
- `str`:person_id,如果未找到返回空字符串
|
- `str`:person_id,如果未找到返回空字符串
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
person_id = person_api.get_person_id_by_name("张三")
|
|
||||||
if person_id:
|
|
||||||
print(f"找到用户: {person_id}")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 1. 基础用户信息获取
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import person_api
|
|
||||||
|
|
||||||
async def get_user_info(platform: str, user_id: int):
|
|
||||||
"""获取用户基本信息"""
|
|
||||||
|
|
||||||
# 获取person_id
|
|
||||||
person_id = person_api.get_person_id(platform, user_id)
|
|
||||||
|
|
||||||
# 获取用户信息
|
|
||||||
user_info = await person_api.get_person_values(
|
|
||||||
person_id,
|
|
||||||
["nickname", "impression", "know_times", "last_seen"],
|
|
||||||
{
|
|
||||||
"nickname": "未知用户",
|
|
||||||
"impression": "",
|
|
||||||
"know_times": 0,
|
|
||||||
"last_seen": 0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"person_id": person_id,
|
|
||||||
"nickname": user_info["nickname"],
|
|
||||||
"impression": user_info["impression"],
|
|
||||||
"know_times": user_info["know_times"],
|
|
||||||
"last_seen": user_info["last_seen"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 在Action中使用用户信息
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.base import BaseAction
|
|
||||||
|
|
||||||
class PersonalizedAction(BaseAction):
|
|
||||||
async def execute(self, action_data, chat_stream):
|
|
||||||
# 获取发送者信息
|
|
||||||
user_id = chat_stream.user_info.user_id
|
|
||||||
platform = chat_stream.platform
|
|
||||||
|
|
||||||
# 获取person_id
|
|
||||||
person_id = person_api.get_person_id(platform, user_id)
|
|
||||||
|
|
||||||
# 获取用户昵称和印象
|
|
||||||
nickname = await person_api.get_person_value(person_id, "nickname", "朋友")
|
|
||||||
impression = await person_api.get_person_value(person_id, "impression", "")
|
|
||||||
|
|
||||||
# 根据用户信息个性化回复
|
|
||||||
if impression:
|
|
||||||
response = f"你好 {nickname}!根据我对你的了解:{impression}"
|
|
||||||
else:
|
|
||||||
response = f"你好 {nickname}!很高兴见到你。"
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"response": response,
|
|
||||||
"user_info": {
|
|
||||||
"nickname": nickname,
|
|
||||||
"impression": impression
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 用户识别和欢迎
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def welcome_user(chat_stream):
|
|
||||||
"""欢迎用户,区分新老用户"""
|
|
||||||
|
|
||||||
user_id = chat_stream.user_info.user_id
|
|
||||||
platform = chat_stream.platform
|
|
||||||
|
|
||||||
# 检查是否认识这个用户
|
|
||||||
is_known = await person_api.is_person_known(platform, user_id)
|
|
||||||
|
|
||||||
if is_known:
|
|
||||||
# 老用户,获取详细信息
|
|
||||||
person_id = person_api.get_person_id(platform, user_id)
|
|
||||||
nickname = await person_api.get_person_value(person_id, "nickname", "老朋友")
|
|
||||||
know_times = await person_api.get_person_value(person_id, "know_times", 0)
|
|
||||||
|
|
||||||
welcome_msg = f"欢迎回来,{nickname}!我们已经聊过 {know_times} 次了。"
|
|
||||||
else:
|
|
||||||
# 新用户
|
|
||||||
welcome_msg = "你好!很高兴认识你,我是MaiBot。"
|
|
||||||
|
|
||||||
return welcome_msg
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 用户搜索功能
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def find_user_by_name(name: str):
|
|
||||||
"""根据名字查找用户"""
|
|
||||||
|
|
||||||
person_id = person_api.get_person_id_by_name(name)
|
|
||||||
|
|
||||||
if not person_id:
|
|
||||||
return {"found": False, "message": f"未找到名为 '{name}' 的用户"}
|
|
||||||
|
|
||||||
# 获取用户详细信息
|
|
||||||
user_info = await person_api.get_person_values(
|
|
||||||
person_id,
|
|
||||||
["nickname", "platform", "user_id", "impression", "know_times"],
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"found": True,
|
|
||||||
"person_id": person_id,
|
|
||||||
"info": user_info
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 用户印象分析
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def analyze_user_relationship(chat_stream):
|
|
||||||
"""分析用户关系"""
|
|
||||||
|
|
||||||
user_id = chat_stream.user_info.user_id
|
|
||||||
platform = chat_stream.platform
|
|
||||||
person_id = person_api.get_person_id(platform, user_id)
|
|
||||||
|
|
||||||
# 获取关系相关信息
|
|
||||||
relationship_info = await person_api.get_person_values(
|
|
||||||
person_id,
|
|
||||||
["nickname", "impression", "know_times", "relationship_level", "last_interaction"],
|
|
||||||
{
|
|
||||||
"nickname": "未知",
|
|
||||||
"impression": "",
|
|
||||||
"know_times": 0,
|
|
||||||
"relationship_level": "stranger",
|
|
||||||
"last_interaction": 0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 分析关系程度
|
|
||||||
know_times = relationship_info["know_times"]
|
|
||||||
if know_times == 0:
|
|
||||||
relationship = "陌生人"
|
|
||||||
elif know_times < 5:
|
|
||||||
relationship = "新朋友"
|
|
||||||
elif know_times < 20:
|
|
||||||
relationship = "熟人"
|
|
||||||
else:
|
|
||||||
relationship = "老朋友"
|
|
||||||
|
|
||||||
return {
|
|
||||||
"nickname": relationship_info["nickname"],
|
|
||||||
"relationship": relationship,
|
|
||||||
"impression": relationship_info["impression"],
|
|
||||||
"interaction_count": know_times
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常用字段说明
|
## 常用字段说明
|
||||||
|
|
||||||
### 基础信息字段
|
### 基础信息字段
|
||||||
@@ -274,69 +107,13 @@ async def analyze_user_relationship(chat_stream):
|
|||||||
|
|
||||||
### 关系信息字段
|
### 关系信息字段
|
||||||
- `impression`:对用户的印象
|
- `impression`:对用户的印象
|
||||||
- `know_times`:交互次数
|
- `points`: 用户特征点
|
||||||
- `relationship_level`:关系等级
|
|
||||||
- `last_seen`:最后见面时间
|
|
||||||
- `last_interaction`:最后交互时间
|
|
||||||
|
|
||||||
### 个性化字段
|
其他字段可以参考`PersonInfo`类的属性(位于`src.common.database.database_model`)
|
||||||
- `preferences`:用户偏好
|
|
||||||
- `interests`:兴趣爱好
|
|
||||||
- `mood_history`:情绪历史
|
|
||||||
- `topic_interests`:话题兴趣
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 错误处理
|
|
||||||
```python
|
|
||||||
async def safe_get_user_info(person_id: str, field: str):
|
|
||||||
"""安全获取用户信息"""
|
|
||||||
try:
|
|
||||||
value = await person_api.get_person_value(person_id, field)
|
|
||||||
return value if value is not None else "未设置"
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"获取用户信息失败: {e}")
|
|
||||||
return "获取失败"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 批量操作
|
|
||||||
```python
|
|
||||||
async def get_complete_user_profile(person_id: str):
|
|
||||||
"""获取完整用户档案"""
|
|
||||||
|
|
||||||
# 一次性获取所有需要的字段
|
|
||||||
fields = [
|
|
||||||
"nickname", "impression", "know_times",
|
|
||||||
"preferences", "interests", "relationship_level"
|
|
||||||
]
|
|
||||||
|
|
||||||
defaults = {
|
|
||||||
"nickname": "用户",
|
|
||||||
"impression": "",
|
|
||||||
"know_times": 0,
|
|
||||||
"preferences": "{}",
|
|
||||||
"interests": "[]",
|
|
||||||
"relationship_level": "stranger"
|
|
||||||
}
|
|
||||||
|
|
||||||
profile = await person_api.get_person_values(person_id, fields, defaults)
|
|
||||||
|
|
||||||
# 处理JSON字段
|
|
||||||
try:
|
|
||||||
profile["preferences"] = json.loads(profile["preferences"])
|
|
||||||
profile["interests"] = json.loads(profile["interests"])
|
|
||||||
except:
|
|
||||||
profile["preferences"] = {}
|
|
||||||
profile["interests"] = []
|
|
||||||
|
|
||||||
return profile
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **异步操作**:大部分查询函数都是异步的,需要使用`await`
|
1. **异步操作**:部分查询函数都是异步的,需要使用`await`
|
||||||
2. **错误处理**:所有函数都有错误处理,失败时记录日志并返回默认值
|
2. **性能考虑**:批量查询优于单个查询
|
||||||
3. **数据类型**:返回的数据可能是字符串、数字或JSON,需要适当处理
|
3. **隐私保护**:确保用户信息的使用符合隐私政策
|
||||||
4. **性能考虑**:批量查询优于单个查询
|
4. **数据一致性**:person_id是用户的唯一标识,应妥善保存和使用
|
||||||
5. **隐私保护**:确保用户信息的使用符合隐私政策
|
|
||||||
6. **数据一致性**:person_id是用户的唯一标识,应妥善保存和使用
|
|
||||||
105
docs/plugins/api/plugin-manage-api.md
Normal file
105
docs/plugins/api/plugin-manage-api.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# 插件管理API
|
||||||
|
|
||||||
|
插件管理API模块提供了对插件的加载、卸载、重新加载以及目录管理功能。
|
||||||
|
|
||||||
|
## 导入方式
|
||||||
|
```python
|
||||||
|
from src.plugin_system.apis import plugin_manage_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import plugin_manage_api
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
插件管理API主要提供以下功能:
|
||||||
|
- **插件查询** - 列出当前加载的插件或已注册的插件。
|
||||||
|
- **插件管理** - 加载、卸载、重新加载插件。
|
||||||
|
- **插件目录管理** - 添加插件目录并重新扫描。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 1. 列出当前加载的插件
|
||||||
|
```python
|
||||||
|
def list_loaded_plugins() -> List[str]:
|
||||||
|
```
|
||||||
|
列出所有当前加载的插件。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `List[str]` - 当前加载的插件名称列表。
|
||||||
|
|
||||||
|
### 2. 列出所有已注册的插件
|
||||||
|
```python
|
||||||
|
def list_registered_plugins() -> List[str]:
|
||||||
|
```
|
||||||
|
列出所有已注册的插件。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `List[str]` - 已注册的插件名称列表。
|
||||||
|
|
||||||
|
### 3. 获取插件路径
|
||||||
|
```python
|
||||||
|
def get_plugin_path(plugin_name: str) -> str:
|
||||||
|
```
|
||||||
|
获取指定插件的路径。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `plugin_name` (str): 要查询的插件名称。
|
||||||
|
**Returns:**
|
||||||
|
- `str` - 插件的路径,如果插件不存在则 raise ValueError。
|
||||||
|
|
||||||
|
### 4. 卸载指定的插件
|
||||||
|
```python
|
||||||
|
async def remove_plugin(plugin_name: str) -> bool:
|
||||||
|
```
|
||||||
|
卸载指定的插件。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `plugin_name` (str): 要卸载的插件名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `bool` - 卸载是否成功。
|
||||||
|
|
||||||
|
### 5. 重新加载指定的插件
|
||||||
|
```python
|
||||||
|
async def reload_plugin(plugin_name: str) -> bool:
|
||||||
|
```
|
||||||
|
重新加载指定的插件。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `plugin_name` (str): 要重新加载的插件名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `bool` - 重新加载是否成功。
|
||||||
|
|
||||||
|
### 6. 加载指定的插件
|
||||||
|
```python
|
||||||
|
def load_plugin(plugin_name: str) -> Tuple[bool, int]:
|
||||||
|
```
|
||||||
|
加载指定的插件。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `plugin_name` (str): 要加载的插件名称。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Tuple[bool, int]` - 加载是否成功,成功或失败的个数。
|
||||||
|
|
||||||
|
### 7. 添加插件目录
|
||||||
|
```python
|
||||||
|
def add_plugin_directory(plugin_directory: str) -> bool:
|
||||||
|
```
|
||||||
|
添加插件目录。
|
||||||
|
|
||||||
|
**Args:**
|
||||||
|
- `plugin_directory` (str): 要添加的插件目录路径。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `bool` - 添加是否成功。
|
||||||
|
|
||||||
|
### 8. 重新扫描插件目录
|
||||||
|
```python
|
||||||
|
def rescan_plugin_directory() -> Tuple[int, int]:
|
||||||
|
```
|
||||||
|
重新扫描插件目录,加载新插件。
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- `Tuple[int, int]` - 成功加载的插件数量和失败的插件数量。
|
||||||
@@ -6,86 +6,108 @@
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import send_api
|
from src.plugin_system.apis import send_api
|
||||||
|
# 或者
|
||||||
|
from src.plugin_system import send_api
|
||||||
```
|
```
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
### 1. 文本消息发送
|
### 1. 发送文本消息
|
||||||
|
```python
|
||||||
|
async def text_to_stream(
|
||||||
|
text: str,
|
||||||
|
stream_id: str,
|
||||||
|
typing: bool = False,
|
||||||
|
reply_to: str = "",
|
||||||
|
storage_message: bool = True,
|
||||||
|
) -> bool:
|
||||||
|
```
|
||||||
|
发送文本消息到指定的流
|
||||||
|
|
||||||
#### `text_to_group(text, group_id, platform="qq", typing=False, reply_to="", storage_message=True)`
|
**Args:**
|
||||||
向群聊发送文本消息
|
- `text` (str): 要发送的文本内容
|
||||||
|
- `stream_id` (str): 聊天流ID
|
||||||
|
- `typing` (bool): 是否显示正在输入
|
||||||
|
- `reply_to` (str): 回复消息,格式为"发送者:消息内容"
|
||||||
|
- `storage_message` (bool): 是否存储消息到数据库
|
||||||
|
|
||||||
**参数:**
|
**Returns:**
|
||||||
- `text`:要发送的文本内容
|
- `bool` - 是否发送成功
|
||||||
- `group_id`:群聊ID
|
|
||||||
- `platform`:平台,默认为"qq"
|
|
||||||
- `typing`:是否显示正在输入
|
|
||||||
- `reply_to`:回复消息的格式,如"发送者:消息内容"
|
|
||||||
- `storage_message`:是否存储到数据库
|
|
||||||
|
|
||||||
**返回:**
|
### 2. 发送表情包
|
||||||
- `bool`:是否发送成功
|
```python
|
||||||
|
async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool:
|
||||||
|
```
|
||||||
|
向指定流发送表情包。
|
||||||
|
|
||||||
#### `text_to_user(text, user_id, platform="qq", typing=False, reply_to="", storage_message=True)`
|
**Args:**
|
||||||
向用户发送私聊文本消息
|
- `emoji_base64` (str): 表情包的base64编码
|
||||||
|
- `stream_id` (str): 聊天流ID
|
||||||
|
- `storage_message` (bool): 是否存储消息到数据库
|
||||||
|
|
||||||
**参数与返回值同上**
|
**Returns:**
|
||||||
|
- `bool` - 是否发送成功
|
||||||
|
|
||||||
### 2. 表情包发送
|
### 3. 发送图片
|
||||||
|
```python
|
||||||
|
async def image_to_stream(image_base64: str, stream_id: str, storage_message: bool = True) -> bool:
|
||||||
|
```
|
||||||
|
向指定流发送图片。
|
||||||
|
|
||||||
#### `emoji_to_group(emoji_base64, group_id, platform="qq", storage_message=True)`
|
**Args:**
|
||||||
向群聊发送表情包
|
- `image_base64` (str): 图片的base64编码
|
||||||
|
- `stream_id` (str): 聊天流ID
|
||||||
|
- `storage_message` (bool): 是否存储消息到数据库
|
||||||
|
|
||||||
**参数:**
|
**Returns:**
|
||||||
- `emoji_base64`:表情包的base64编码
|
- `bool` - 是否发送成功
|
||||||
- `group_id`:群聊ID
|
|
||||||
- `platform`:平台,默认为"qq"
|
|
||||||
- `storage_message`:是否存储到数据库
|
|
||||||
|
|
||||||
#### `emoji_to_user(emoji_base64, user_id, platform="qq", storage_message=True)`
|
### 4. 发送命令
|
||||||
向用户发送表情包
|
```python
|
||||||
|
async def command_to_stream(command: Union[str, dict], stream_id: str, storage_message: bool = True, display_message: str = "") -> bool:
|
||||||
|
```
|
||||||
|
向指定流发送命令。
|
||||||
|
|
||||||
### 3. 图片发送
|
**Args:**
|
||||||
|
- `command` (Union[str, dict]): 命令内容
|
||||||
|
- `stream_id` (str): 聊天流ID
|
||||||
|
- `storage_message` (bool): 是否存储消息到数据库
|
||||||
|
- `display_message` (str): 显示消息
|
||||||
|
|
||||||
#### `image_to_group(image_base64, group_id, platform="qq", storage_message=True)`
|
**Returns:**
|
||||||
向群聊发送图片
|
- `bool` - 是否发送成功
|
||||||
|
|
||||||
#### `image_to_user(image_base64, user_id, platform="qq", storage_message=True)`
|
### 5. 发送自定义类型消息
|
||||||
向用户发送图片
|
```python
|
||||||
|
async def custom_to_stream(
|
||||||
|
message_type: str,
|
||||||
|
content: str,
|
||||||
|
stream_id: str,
|
||||||
|
display_message: str = "",
|
||||||
|
typing: bool = False,
|
||||||
|
reply_to: str = "",
|
||||||
|
storage_message: bool = True,
|
||||||
|
show_log: bool = True,
|
||||||
|
) -> bool:
|
||||||
|
```
|
||||||
|
向指定流发送自定义类型消息。
|
||||||
|
|
||||||
### 4. 命令发送
|
**Args:**
|
||||||
|
- `message_type` (str): 消息类型,如"text"、"image"、"emoji"、"video"、"file"等
|
||||||
|
- `content` (str): 消息内容(通常是base64编码或文本)
|
||||||
|
- `stream_id` (str): 聊天流ID
|
||||||
|
- `display_message` (str): 显示消息
|
||||||
|
- `typing` (bool): 是否显示正在输入
|
||||||
|
- `reply_to` (str): 回复消息,格式为"发送者:消息内容"
|
||||||
|
- `storage_message` (bool): 是否存储消息到数据库
|
||||||
|
- `show_log` (bool): 是否显示日志
|
||||||
|
|
||||||
#### `command_to_group(command, group_id, platform="qq", storage_message=True)`
|
**Returns:**
|
||||||
向群聊发送命令
|
- `bool` - 是否发送成功
|
||||||
|
|
||||||
#### `command_to_user(command, user_id, platform="qq", storage_message=True)`
|
|
||||||
向用户发送命令
|
|
||||||
|
|
||||||
### 5. 自定义消息发送
|
|
||||||
|
|
||||||
#### `custom_to_group(message_type, content, group_id, platform="qq", display_message="", typing=False, reply_to="", storage_message=True)`
|
|
||||||
向群聊发送自定义类型消息
|
|
||||||
|
|
||||||
#### `custom_to_user(message_type, content, user_id, platform="qq", display_message="", typing=False, reply_to="", storage_message=True)`
|
|
||||||
向用户发送自定义类型消息
|
|
||||||
|
|
||||||
#### `custom_message(message_type, content, target_id, is_group=True, platform="qq", display_message="", typing=False, reply_to="", storage_message=True)`
|
|
||||||
通用的自定义消息发送
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `message_type`:消息类型,如"text"、"image"、"emoji"等
|
|
||||||
- `content`:消息内容
|
|
||||||
- `target_id`:目标ID(群ID或用户ID)
|
|
||||||
- `is_group`:是否为群聊
|
|
||||||
- `platform`:平台
|
|
||||||
- `display_message`:显示消息
|
|
||||||
- `typing`:是否显示正在输入
|
|
||||||
- `reply_to`:回复消息
|
|
||||||
- `storage_message`:是否存储
|
|
||||||
|
|
||||||
## 使用示例
|
## 使用示例
|
||||||
|
|
||||||
### 1. 基础文本发送
|
### 1. 基础文本发送,并回复消息
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.apis import send_api
|
from src.plugin_system.apis import send_api
|
||||||
@@ -93,57 +115,23 @@ from src.plugin_system.apis import send_api
|
|||||||
async def send_hello(chat_stream):
|
async def send_hello(chat_stream):
|
||||||
"""发送问候消息"""
|
"""发送问候消息"""
|
||||||
|
|
||||||
if chat_stream.group_info:
|
success = await send_api.text_to_stream(
|
||||||
# 群聊
|
text="Hello, world!",
|
||||||
success = await send_api.text_to_group(
|
stream_id=chat_stream.stream_id,
|
||||||
text="大家好!",
|
typing=True,
|
||||||
group_id=chat_stream.group_info.group_id,
|
reply_to="User:How are you?",
|
||||||
typing=True
|
storage_message=True
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
# 私聊
|
|
||||||
success = await send_api.text_to_user(
|
|
||||||
text="你好!",
|
|
||||||
user_id=chat_stream.user_info.user_id,
|
|
||||||
typing=True
|
|
||||||
)
|
|
||||||
|
|
||||||
return success
|
return success
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 回复特定消息
|
### 2. 发送表情包
|
||||||
|
|
||||||
```python
|
|
||||||
async def reply_to_message(chat_stream, reply_text, original_sender, original_message):
|
|
||||||
"""回复特定消息"""
|
|
||||||
|
|
||||||
# 构建回复格式
|
|
||||||
reply_to = f"{original_sender}:{original_message}"
|
|
||||||
|
|
||||||
if chat_stream.group_info:
|
|
||||||
success = await send_api.text_to_group(
|
|
||||||
text=reply_text,
|
|
||||||
group_id=chat_stream.group_info.group_id,
|
|
||||||
reply_to=reply_to
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
success = await send_api.text_to_user(
|
|
||||||
text=reply_text,
|
|
||||||
user_id=chat_stream.user_info.user_id,
|
|
||||||
reply_to=reply_to
|
|
||||||
)
|
|
||||||
|
|
||||||
return success
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 发送表情包
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from src.plugin_system.apis import emoji_api
|
||||||
async def send_emoji_reaction(chat_stream, emotion):
|
async def send_emoji_reaction(chat_stream, emotion):
|
||||||
"""根据情感发送表情包"""
|
"""根据情感发送表情包"""
|
||||||
|
|
||||||
from src.plugin_system.apis import emoji_api
|
|
||||||
|
|
||||||
# 获取表情包
|
# 获取表情包
|
||||||
emoji_result = await emoji_api.get_by_emotion(emotion)
|
emoji_result = await emoji_api.get_by_emotion(emotion)
|
||||||
if not emoji_result:
|
if not emoji_result:
|
||||||
@@ -152,107 +140,10 @@ async def send_emoji_reaction(chat_stream, emotion):
|
|||||||
emoji_base64, description, matched_emotion = emoji_result
|
emoji_base64, description, matched_emotion = emoji_result
|
||||||
|
|
||||||
# 发送表情包
|
# 发送表情包
|
||||||
if chat_stream.group_info:
|
success = await send_api.emoji_to_stream(
|
||||||
success = await send_api.emoji_to_group(
|
emoji_base64=emoji_base64,
|
||||||
emoji_base64=emoji_base64,
|
stream_id=chat_stream.stream_id,
|
||||||
group_id=chat_stream.group_info.group_id
|
storage_message=False # 不存储到数据库
|
||||||
)
|
|
||||||
else:
|
|
||||||
success = await send_api.emoji_to_user(
|
|
||||||
emoji_base64=emoji_base64,
|
|
||||||
user_id=chat_stream.user_info.user_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return success
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 在Action中发送消息
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.base import BaseAction
|
|
||||||
|
|
||||||
class MessageAction(BaseAction):
|
|
||||||
async def execute(self, action_data, chat_stream):
|
|
||||||
message_type = action_data.get("type", "text")
|
|
||||||
content = action_data.get("content", "")
|
|
||||||
|
|
||||||
if message_type == "text":
|
|
||||||
success = await self.send_text(chat_stream, content)
|
|
||||||
elif message_type == "emoji":
|
|
||||||
success = await self.send_emoji(chat_stream, content)
|
|
||||||
elif message_type == "image":
|
|
||||||
success = await self.send_image(chat_stream, content)
|
|
||||||
else:
|
|
||||||
success = False
|
|
||||||
|
|
||||||
return {"success": success}
|
|
||||||
|
|
||||||
async def send_text(self, chat_stream, text):
|
|
||||||
if chat_stream.group_info:
|
|
||||||
return await send_api.text_to_group(text, chat_stream.group_info.group_id)
|
|
||||||
else:
|
|
||||||
return await send_api.text_to_user(text, chat_stream.user_info.user_id)
|
|
||||||
|
|
||||||
async def send_emoji(self, chat_stream, emoji_base64):
|
|
||||||
if chat_stream.group_info:
|
|
||||||
return await send_api.emoji_to_group(emoji_base64, chat_stream.group_info.group_id)
|
|
||||||
else:
|
|
||||||
return await send_api.emoji_to_user(emoji_base64, chat_stream.user_info.user_id)
|
|
||||||
|
|
||||||
async def send_image(self, chat_stream, image_base64):
|
|
||||||
if chat_stream.group_info:
|
|
||||||
return await send_api.image_to_group(image_base64, chat_stream.group_info.group_id)
|
|
||||||
else:
|
|
||||||
return await send_api.image_to_user(image_base64, chat_stream.user_info.user_id)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 批量发送消息
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def broadcast_message(message: str, target_groups: list):
|
|
||||||
"""向多个群组广播消息"""
|
|
||||||
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
for group_id in target_groups:
|
|
||||||
try:
|
|
||||||
success = await send_api.text_to_group(
|
|
||||||
text=message,
|
|
||||||
group_id=group_id,
|
|
||||||
typing=True
|
|
||||||
)
|
|
||||||
results[group_id] = success
|
|
||||||
except Exception as e:
|
|
||||||
results[group_id] = False
|
|
||||||
print(f"发送到群 {group_id} 失败: {e}")
|
|
||||||
|
|
||||||
return results
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 智能消息发送
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def smart_send(chat_stream, message_data):
|
|
||||||
"""智能发送不同类型的消息"""
|
|
||||||
|
|
||||||
message_type = message_data.get("type", "text")
|
|
||||||
content = message_data.get("content", "")
|
|
||||||
options = message_data.get("options", {})
|
|
||||||
|
|
||||||
# 根据聊天流类型选择发送方法
|
|
||||||
target_id = (chat_stream.group_info.group_id if chat_stream.group_info
|
|
||||||
else chat_stream.user_info.user_id)
|
|
||||||
is_group = chat_stream.group_info is not None
|
|
||||||
|
|
||||||
# 使用通用发送方法
|
|
||||||
success = await send_api.custom_message(
|
|
||||||
message_type=message_type,
|
|
||||||
content=content,
|
|
||||||
target_id=target_id,
|
|
||||||
is_group=is_group,
|
|
||||||
typing=options.get("typing", False),
|
|
||||||
reply_to=options.get("reply_to", ""),
|
|
||||||
display_message=options.get("display_message", "")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return success
|
return success
|
||||||
@@ -273,90 +164,6 @@ async def smart_send(chat_stream, message_data):
|
|||||||
|
|
||||||
系统会自动查找匹配的原始消息并进行回复。
|
系统会自动查找匹配的原始消息并进行回复。
|
||||||
|
|
||||||
## 高级用法
|
|
||||||
|
|
||||||
### 1. 消息发送队列
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
class MessageQueue:
|
|
||||||
def __init__(self):
|
|
||||||
self.queue = asyncio.Queue()
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
async def add_message(self, chat_stream, message_type, content, options=None):
|
|
||||||
"""添加消息到队列"""
|
|
||||||
message_item = {
|
|
||||||
"chat_stream": chat_stream,
|
|
||||||
"type": message_type,
|
|
||||||
"content": content,
|
|
||||||
"options": options or {}
|
|
||||||
}
|
|
||||||
await self.queue.put(message_item)
|
|
||||||
|
|
||||||
async def process_queue(self):
|
|
||||||
"""处理消息队列"""
|
|
||||||
self.running = True
|
|
||||||
|
|
||||||
while self.running:
|
|
||||||
try:
|
|
||||||
message_item = await asyncio.wait_for(self.queue.get(), timeout=1.0)
|
|
||||||
|
|
||||||
# 发送消息
|
|
||||||
success = await smart_send(
|
|
||||||
message_item["chat_stream"],
|
|
||||||
{
|
|
||||||
"type": message_item["type"],
|
|
||||||
"content": message_item["content"],
|
|
||||||
"options": message_item["options"]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 标记任务完成
|
|
||||||
self.queue.task_done()
|
|
||||||
|
|
||||||
# 发送间隔
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
continue
|
|
||||||
except Exception as e:
|
|
||||||
print(f"处理消息队列出错: {e}")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 消息模板系统
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MessageTemplate:
|
|
||||||
def __init__(self):
|
|
||||||
self.templates = {
|
|
||||||
"welcome": "欢迎 {nickname} 加入群聊!",
|
|
||||||
"goodbye": "{nickname} 离开了群聊。",
|
|
||||||
"notification": "🔔 通知:{message}",
|
|
||||||
"error": "❌ 错误:{error_message}",
|
|
||||||
"success": "✅ 成功:{message}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def format_message(self, template_name: str, **kwargs) -> str:
|
|
||||||
"""格式化消息模板"""
|
|
||||||
template = self.templates.get(template_name, "{message}")
|
|
||||||
return template.format(**kwargs)
|
|
||||||
|
|
||||||
async def send_template(self, chat_stream, template_name: str, **kwargs):
|
|
||||||
"""发送模板消息"""
|
|
||||||
message = self.format_message(template_name, **kwargs)
|
|
||||||
|
|
||||||
if chat_stream.group_info:
|
|
||||||
return await send_api.text_to_group(message, chat_stream.group_info.group_id)
|
|
||||||
else:
|
|
||||||
return await send_api.text_to_user(message, chat_stream.user_info.user_id)
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
template_system = MessageTemplate()
|
|
||||||
await template_system.send_template(chat_stream, "welcome", nickname="张三")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **异步操作**:所有发送函数都是异步的,必须使用`await`
|
1. **异步操作**:所有发送函数都是异步的,必须使用`await`
|
||||||
|
|||||||
@@ -1,435 +0,0 @@
|
|||||||
# 工具API
|
|
||||||
|
|
||||||
工具API模块提供了各种辅助功能,包括文件操作、时间处理、唯一ID生成等常用工具函数。
|
|
||||||
|
|
||||||
## 导入方式
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import utils_api
|
|
||||||
```
|
|
||||||
|
|
||||||
## 主要功能
|
|
||||||
|
|
||||||
### 1. 文件操作
|
|
||||||
|
|
||||||
#### `get_plugin_path(caller_frame=None) -> str`
|
|
||||||
获取调用者插件的路径
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `caller_frame`:调用者的栈帧,默认为None(自动获取)
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `str`:插件目录的绝对路径
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
plugin_path = utils_api.get_plugin_path()
|
|
||||||
print(f"插件路径: {plugin_path}")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `read_json_file(file_path: str, default: Any = None) -> Any`
|
|
||||||
读取JSON文件
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `file_path`:文件路径,可以是相对于插件目录的路径
|
|
||||||
- `default`:如果文件不存在或读取失败时返回的默认值
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `Any`:JSON数据或默认值
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
# 读取插件配置文件
|
|
||||||
config = utils_api.read_json_file("config.json", {})
|
|
||||||
settings = utils_api.read_json_file("data/settings.json", {"enabled": True})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `write_json_file(file_path: str, data: Any, indent: int = 2) -> bool`
|
|
||||||
写入JSON文件
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `file_path`:文件路径,可以是相对于插件目录的路径
|
|
||||||
- `data`:要写入的数据
|
|
||||||
- `indent`:JSON缩进
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `bool`:是否写入成功
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
data = {"name": "test", "value": 123}
|
|
||||||
success = utils_api.write_json_file("output.json", data)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 时间相关
|
|
||||||
|
|
||||||
#### `get_timestamp() -> int`
|
|
||||||
获取当前时间戳
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `int`:当前时间戳(秒)
|
|
||||||
|
|
||||||
#### `format_time(timestamp: Optional[int] = None, format_str: str = "%Y-%m-%d %H:%M:%S") -> str`
|
|
||||||
格式化时间
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `timestamp`:时间戳,如果为None则使用当前时间
|
|
||||||
- `format_str`:时间格式字符串
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `str`:格式化后的时间字符串
|
|
||||||
|
|
||||||
#### `parse_time(time_str: str, format_str: str = "%Y-%m-%d %H:%M:%S") -> int`
|
|
||||||
解析时间字符串为时间戳
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
- `time_str`:时间字符串
|
|
||||||
- `format_str`:时间格式字符串
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `int`:时间戳(秒)
|
|
||||||
|
|
||||||
### 3. 其他工具
|
|
||||||
|
|
||||||
#### `generate_unique_id() -> str`
|
|
||||||
生成唯一ID
|
|
||||||
|
|
||||||
**返回:**
|
|
||||||
- `str`:唯一ID
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 1. 插件数据管理
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.apis import utils_api
|
|
||||||
|
|
||||||
class DataPlugin(BasePlugin):
|
|
||||||
def __init__(self):
|
|
||||||
self.plugin_path = utils_api.get_plugin_path()
|
|
||||||
self.data_file = "plugin_data.json"
|
|
||||||
self.load_data()
|
|
||||||
|
|
||||||
def load_data(self):
|
|
||||||
"""加载插件数据"""
|
|
||||||
default_data = {
|
|
||||||
"users": {},
|
|
||||||
"settings": {"enabled": True},
|
|
||||||
"stats": {"message_count": 0}
|
|
||||||
}
|
|
||||||
self.data = utils_api.read_json_file(self.data_file, default_data)
|
|
||||||
|
|
||||||
def save_data(self):
|
|
||||||
"""保存插件数据"""
|
|
||||||
return utils_api.write_json_file(self.data_file, self.data)
|
|
||||||
|
|
||||||
async def handle_action(self, action_data, chat_stream):
|
|
||||||
# 更新统计信息
|
|
||||||
self.data["stats"]["message_count"] += 1
|
|
||||||
self.data["stats"]["last_update"] = utils_api.get_timestamp()
|
|
||||||
|
|
||||||
# 保存数据
|
|
||||||
if self.save_data():
|
|
||||||
return {"success": True, "message": "数据已保存"}
|
|
||||||
else:
|
|
||||||
return {"success": False, "message": "数据保存失败"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 日志记录系统
|
|
||||||
|
|
||||||
```python
|
|
||||||
class PluginLogger:
|
|
||||||
def __init__(self, plugin_name: str):
|
|
||||||
self.plugin_name = plugin_name
|
|
||||||
self.log_file = f"{plugin_name}_log.json"
|
|
||||||
self.logs = utils_api.read_json_file(self.log_file, [])
|
|
||||||
|
|
||||||
def log_event(self, event_type: str, message: str, data: dict = None):
|
|
||||||
"""记录事件"""
|
|
||||||
log_entry = {
|
|
||||||
"id": utils_api.generate_unique_id(),
|
|
||||||
"timestamp": utils_api.get_timestamp(),
|
|
||||||
"formatted_time": utils_api.format_time(),
|
|
||||||
"event_type": event_type,
|
|
||||||
"message": message,
|
|
||||||
"data": data or {}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.logs.append(log_entry)
|
|
||||||
|
|
||||||
# 保持最新的100条记录
|
|
||||||
if len(self.logs) > 100:
|
|
||||||
self.logs = self.logs[-100:]
|
|
||||||
|
|
||||||
# 保存到文件
|
|
||||||
utils_api.write_json_file(self.log_file, self.logs)
|
|
||||||
|
|
||||||
def get_logs_by_type(self, event_type: str) -> list:
|
|
||||||
"""获取指定类型的日志"""
|
|
||||||
return [log for log in self.logs if log["event_type"] == event_type]
|
|
||||||
|
|
||||||
def get_recent_logs(self, count: int = 10) -> list:
|
|
||||||
"""获取最近的日志"""
|
|
||||||
return self.logs[-count:]
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
logger = PluginLogger("my_plugin")
|
|
||||||
logger.log_event("user_action", "用户发送了消息", {"user_id": "123", "message": "hello"})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 配置管理系统
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ConfigManager:
|
|
||||||
def __init__(self, config_file: str = "plugin_config.json"):
|
|
||||||
self.config_file = config_file
|
|
||||||
self.default_config = {
|
|
||||||
"enabled": True,
|
|
||||||
"debug": False,
|
|
||||||
"max_users": 100,
|
|
||||||
"response_delay": 1.0,
|
|
||||||
"features": {
|
|
||||||
"auto_reply": True,
|
|
||||||
"logging": True
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.config = self.load_config()
|
|
||||||
|
|
||||||
def load_config(self) -> dict:
|
|
||||||
"""加载配置"""
|
|
||||||
return utils_api.read_json_file(self.config_file, self.default_config)
|
|
||||||
|
|
||||||
def save_config(self) -> bool:
|
|
||||||
"""保存配置"""
|
|
||||||
return utils_api.write_json_file(self.config_file, self.config, indent=4)
|
|
||||||
|
|
||||||
def get(self, key: str, default=None):
|
|
||||||
"""获取配置值,支持嵌套访问"""
|
|
||||||
keys = key.split('.')
|
|
||||||
value = self.config
|
|
||||||
|
|
||||||
for k in keys:
|
|
||||||
if isinstance(value, dict) and k in value:
|
|
||||||
value = value[k]
|
|
||||||
else:
|
|
||||||
return default
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def set(self, key: str, value):
|
|
||||||
"""设置配置值,支持嵌套设置"""
|
|
||||||
keys = key.split('.')
|
|
||||||
config = self.config
|
|
||||||
|
|
||||||
for k in keys[:-1]:
|
|
||||||
if k not in config:
|
|
||||||
config[k] = {}
|
|
||||||
config = config[k]
|
|
||||||
|
|
||||||
config[keys[-1]] = value
|
|
||||||
|
|
||||||
def update_config(self, updates: dict):
|
|
||||||
"""批量更新配置"""
|
|
||||||
def deep_update(base, updates):
|
|
||||||
for key, value in updates.items():
|
|
||||||
if isinstance(value, dict) and key in base and isinstance(base[key], dict):
|
|
||||||
deep_update(base[key], value)
|
|
||||||
else:
|
|
||||||
base[key] = value
|
|
||||||
|
|
||||||
deep_update(self.config, updates)
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
config = ConfigManager()
|
|
||||||
print(f"调试模式: {config.get('debug', False)}")
|
|
||||||
print(f"自动回复: {config.get('features.auto_reply', True)}")
|
|
||||||
|
|
||||||
config.set('features.new_feature', True)
|
|
||||||
config.save_config()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 缓存系统
|
|
||||||
|
|
||||||
```python
|
|
||||||
class PluginCache:
|
|
||||||
def __init__(self, cache_file: str = "plugin_cache.json", ttl: int = 3600):
|
|
||||||
self.cache_file = cache_file
|
|
||||||
self.ttl = ttl # 缓存过期时间(秒)
|
|
||||||
self.cache = self.load_cache()
|
|
||||||
|
|
||||||
def load_cache(self) -> dict:
|
|
||||||
"""加载缓存"""
|
|
||||||
return utils_api.read_json_file(self.cache_file, {})
|
|
||||||
|
|
||||||
def save_cache(self):
|
|
||||||
"""保存缓存"""
|
|
||||||
return utils_api.write_json_file(self.cache_file, self.cache)
|
|
||||||
|
|
||||||
def get(self, key: str):
|
|
||||||
"""获取缓存值"""
|
|
||||||
if key not in self.cache:
|
|
||||||
return None
|
|
||||||
|
|
||||||
item = self.cache[key]
|
|
||||||
current_time = utils_api.get_timestamp()
|
|
||||||
|
|
||||||
# 检查是否过期
|
|
||||||
if current_time - item["timestamp"] > self.ttl:
|
|
||||||
del self.cache[key]
|
|
||||||
return None
|
|
||||||
|
|
||||||
return item["value"]
|
|
||||||
|
|
||||||
def set(self, key: str, value):
|
|
||||||
"""设置缓存值"""
|
|
||||||
self.cache[key] = {
|
|
||||||
"value": value,
|
|
||||||
"timestamp": utils_api.get_timestamp()
|
|
||||||
}
|
|
||||||
self.save_cache()
|
|
||||||
|
|
||||||
def clear_expired(self):
|
|
||||||
"""清理过期缓存"""
|
|
||||||
current_time = utils_api.get_timestamp()
|
|
||||||
expired_keys = []
|
|
||||||
|
|
||||||
for key, item in self.cache.items():
|
|
||||||
if current_time - item["timestamp"] > self.ttl:
|
|
||||||
expired_keys.append(key)
|
|
||||||
|
|
||||||
for key in expired_keys:
|
|
||||||
del self.cache[key]
|
|
||||||
|
|
||||||
if expired_keys:
|
|
||||||
self.save_cache()
|
|
||||||
|
|
||||||
return len(expired_keys)
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
cache = PluginCache(ttl=1800) # 30分钟过期
|
|
||||||
cache.set("user_data_123", {"name": "张三", "score": 100})
|
|
||||||
user_data = cache.get("user_data_123")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 时间处理工具
|
|
||||||
|
|
||||||
```python
|
|
||||||
class TimeHelper:
|
|
||||||
@staticmethod
|
|
||||||
def get_time_info():
|
|
||||||
"""获取当前时间的详细信息"""
|
|
||||||
timestamp = utils_api.get_timestamp()
|
|
||||||
return {
|
|
||||||
"timestamp": timestamp,
|
|
||||||
"datetime": utils_api.format_time(timestamp),
|
|
||||||
"date": utils_api.format_time(timestamp, "%Y-%m-%d"),
|
|
||||||
"time": utils_api.format_time(timestamp, "%H:%M:%S"),
|
|
||||||
"year": utils_api.format_time(timestamp, "%Y"),
|
|
||||||
"month": utils_api.format_time(timestamp, "%m"),
|
|
||||||
"day": utils_api.format_time(timestamp, "%d"),
|
|
||||||
"weekday": utils_api.format_time(timestamp, "%A")
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def time_ago(timestamp: int) -> str:
|
|
||||||
"""计算时间差"""
|
|
||||||
current = utils_api.get_timestamp()
|
|
||||||
diff = current - timestamp
|
|
||||||
|
|
||||||
if diff < 60:
|
|
||||||
return f"{diff}秒前"
|
|
||||||
elif diff < 3600:
|
|
||||||
return f"{diff // 60}分钟前"
|
|
||||||
elif diff < 86400:
|
|
||||||
return f"{diff // 3600}小时前"
|
|
||||||
else:
|
|
||||||
return f"{diff // 86400}天前"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_duration(duration_str: str) -> int:
|
|
||||||
"""解析时间段字符串,返回秒数"""
|
|
||||||
import re
|
|
||||||
|
|
||||||
pattern = r'(\d+)([smhd])'
|
|
||||||
matches = re.findall(pattern, duration_str.lower())
|
|
||||||
|
|
||||||
total_seconds = 0
|
|
||||||
for value, unit in matches:
|
|
||||||
value = int(value)
|
|
||||||
if unit == 's':
|
|
||||||
total_seconds += value
|
|
||||||
elif unit == 'm':
|
|
||||||
total_seconds += value * 60
|
|
||||||
elif unit == 'h':
|
|
||||||
total_seconds += value * 3600
|
|
||||||
elif unit == 'd':
|
|
||||||
total_seconds += value * 86400
|
|
||||||
|
|
||||||
return total_seconds
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
time_info = TimeHelper.get_time_info()
|
|
||||||
print(f"当前时间: {time_info['datetime']}")
|
|
||||||
|
|
||||||
last_seen = 1699000000
|
|
||||||
print(f"最后见面: {TimeHelper.time_ago(last_seen)}")
|
|
||||||
|
|
||||||
duration = TimeHelper.parse_duration("1h30m") # 1小时30分钟 = 5400秒
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 错误处理
|
|
||||||
```python
|
|
||||||
def safe_file_operation(file_path: str, data: dict):
|
|
||||||
"""安全的文件操作"""
|
|
||||||
try:
|
|
||||||
success = utils_api.write_json_file(file_path, data)
|
|
||||||
if not success:
|
|
||||||
logger.warning(f"文件写入失败: {file_path}")
|
|
||||||
return success
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"文件操作出错: {e}")
|
|
||||||
return False
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 路径处理
|
|
||||||
```python
|
|
||||||
import os
|
|
||||||
|
|
||||||
def get_data_path(filename: str) -> str:
|
|
||||||
"""获取数据文件的完整路径"""
|
|
||||||
plugin_path = utils_api.get_plugin_path()
|
|
||||||
data_dir = os.path.join(plugin_path, "data")
|
|
||||||
|
|
||||||
# 确保数据目录存在
|
|
||||||
os.makedirs(data_dir, exist_ok=True)
|
|
||||||
|
|
||||||
return os.path.join(data_dir, filename)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 定期清理
|
|
||||||
```python
|
|
||||||
async def cleanup_old_files():
|
|
||||||
"""清理旧文件"""
|
|
||||||
plugin_path = utils_api.get_plugin_path()
|
|
||||||
current_time = utils_api.get_timestamp()
|
|
||||||
|
|
||||||
for filename in os.listdir(plugin_path):
|
|
||||||
if filename.endswith('.tmp'):
|
|
||||||
file_path = os.path.join(plugin_path, filename)
|
|
||||||
file_time = os.path.getmtime(file_path)
|
|
||||||
|
|
||||||
# 删除超过24小时的临时文件
|
|
||||||
if current_time - file_time > 86400:
|
|
||||||
os.remove(file_path)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **相对路径**:文件路径支持相对于插件目录的路径
|
|
||||||
2. **自动创建目录**:写入文件时会自动创建必要的目录
|
|
||||||
3. **错误处理**:所有函数都有错误处理,失败时返回默认值
|
|
||||||
4. **编码格式**:文件读写使用UTF-8编码
|
|
||||||
5. **时间格式**:时间戳使用秒为单位
|
|
||||||
6. **JSON格式**:JSON文件使用可读性好的缩进格式
|
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
## 📖 什么是Command
|
## 📖 什么是Command
|
||||||
|
|
||||||
Command是直接响应用户明确指令的组件,与Action不同,Command是**被动触发**的,当用户输入特定格式的命令时立即执行。Command通过正则表达式匹配用户输入,提供确定性的功能服务。
|
Command是直接响应用户明确指令的组件,与Action不同,Command是**被动触发**的,当用户输入特定格式的命令时立即执行。
|
||||||
|
|
||||||
|
Command通过正则表达式匹配用户输入,提供确定性的功能服务。
|
||||||
|
|
||||||
### 🎯 Command的特点
|
### 🎯 Command的特点
|
||||||
|
|
||||||
@@ -12,501 +14,76 @@ Command是直接响应用户明确指令的组件,与Action不同,Command是
|
|||||||
- 🛑 **拦截控制**:可以控制是否阻止消息继续处理
|
- 🛑 **拦截控制**:可以控制是否阻止消息继续处理
|
||||||
- 📝 **参数解析**:支持从用户输入中提取参数
|
- 📝 **参数解析**:支持从用户输入中提取参数
|
||||||
|
|
||||||
## 🆚 Action vs Command 核心区别
|
---
|
||||||
|
|
||||||
| 特征 | Action | Command |
|
## 🛠️ Command组件的基本结构
|
||||||
| ------------------ | --------------------- | ---------------- |
|
|
||||||
| **触发方式** | 麦麦主动决策使用 | 用户主动触发 |
|
|
||||||
| **决策机制** | 两层决策(激活+使用) | 直接匹配执行 |
|
|
||||||
| **随机性** | 有随机性和智能性 | 确定性执行 |
|
|
||||||
| **用途** | 增强麦麦行为拟人化 | 提供具体功能服务 |
|
|
||||||
| **性能影响** | 需要LLM决策 | 正则匹配,性能好 |
|
|
||||||
|
|
||||||
## 🏗️ Command基本结构
|
首先,Command组件需要继承自`BaseCommand`类,并实现必要的方法。
|
||||||
|
|
||||||
### 必须属性
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system import BaseCommand
|
class ExampleCommand(BaseCommand):
|
||||||
|
command_name = "example" # 命令名称,作为唯一标识符
|
||||||
|
command_description = "这是一个示例命令" # 命令描述
|
||||||
|
command_pattern = r"" # 命令匹配的正则表达式
|
||||||
|
|
||||||
class MyCommand(BaseCommand):
|
async def execute(self) -> Tuple[bool, Optional[str], bool]:
|
||||||
# 正则表达式匹配模式
|
"""
|
||||||
command_pattern = r"^/help\s+(?P<topic>\w+)$"
|
执行Command的主要逻辑
|
||||||
|
|
||||||
# 命令帮助说明
|
Returns:
|
||||||
command_help = "显示指定主题的帮助信息"
|
Tuple[bool, str, bool]:
|
||||||
|
- 第一个bool表示是否成功执行
|
||||||
# 使用示例
|
- 第二个str是执行结果消息
|
||||||
command_examples = ["/help action", "/help command"]
|
- 第三个bool表示是否需要阻止消息继续处理
|
||||||
|
"""
|
||||||
# 是否拦截后续处理
|
# ---- 执行命令的逻辑 ----
|
||||||
intercept_message = True
|
return True, "执行成功", False
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
"""执行命令逻辑"""
|
|
||||||
# 命令执行逻辑
|
|
||||||
return True, "执行成功"
|
|
||||||
```
|
```
|
||||||
|
**`command_pattern`**: 该Command匹配的正则表达式,用于精确匹配用户输入。
|
||||||
|
|
||||||
### 属性说明
|
请注意:如果希望能获取到命令中的参数,请在正则表达式中使用有命名的捕获组,例如`(?P<param_name>pattern)`。
|
||||||
|
|
||||||
| 属性 | 类型 | 说明 |
|
这样在匹配时,内部实现可以使用`re.match.groupdict()`方法获取到所有捕获组的参数,并以字典的形式存储在`self.matched_groups`中。
|
||||||
| --------------------- | --------- | -------------------- |
|
|
||||||
| `command_pattern` | str | 正则表达式匹配模式 |
|
|
||||||
| `command_help` | str | 命令帮助说明 |
|
|
||||||
| `command_examples` | List[str] | 使用示例列表 |
|
|
||||||
| `intercept_message` | bool | 是否拦截消息继续处理 |
|
|
||||||
|
|
||||||
## 🔍 正则表达式匹配
|
### 匹配样例
|
||||||
|
假设我们有一个命令`/example param1=value1 param2=value2`,对应的正则表达式可以是:
|
||||||
### 基础匹配
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class SimpleCommand(BaseCommand):
|
class ExampleCommand(BaseCommand):
|
||||||
# 匹配 /ping
|
command_name = "example"
|
||||||
command_pattern = r"^/ping$"
|
command_description = "这是一个示例命令"
|
||||||
|
command_pattern = r"/example (?P<param1>\w+) (?P<param2>\w+)"
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
await self.send_text("Pong!")
|
async def execute(self) -> Tuple[bool, Optional[str], bool]:
|
||||||
return True, "发送了Pong回复"
|
# 获取匹配的参数
|
||||||
|
param1 = self.matched_groups.get("param1")
|
||||||
|
param2 = self.matched_groups.get("param2")
|
||||||
|
|
||||||
|
# 执行逻辑
|
||||||
|
return True, f"参数1: {param1}, 参数2: {param2}", False
|
||||||
```
|
```
|
||||||
|
|
||||||
### 参数捕获
|
---
|
||||||
|
|
||||||
使用命名组 `(?P<n>pattern)` 捕获参数:
|
|
||||||
|
|
||||||
|
## Command 内置方法说明
|
||||||
```python
|
```python
|
||||||
class UserCommand(BaseCommand):
|
class BaseCommand:
|
||||||
# 匹配 /user add 张三 或 /user del 李四
|
def get_config(self, key: str, default=None):
|
||||||
command_pattern = r"^/user\s+(?P<action>add|del|info)\s+(?P<username>\w+)$"
|
"""获取插件配置值,使用嵌套键访问"""
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
async def send_text(self, content: str, reply_to: str = "") -> bool:
|
||||||
# 通过 self.matched_groups 获取捕获的参数
|
"""发送回复消息"""
|
||||||
action = self.matched_groups.get("action")
|
|
||||||
username = self.matched_groups.get("username")
|
async def send_type(self, message_type: str, content: str, display_message: str = "", typing: bool = False, reply_to: str = "") -> bool:
|
||||||
|
"""发送指定类型的回复消息到当前聊天环境"""
|
||||||
if action == "add":
|
|
||||||
await self.send_text(f"添加用户:{username}")
|
async def send_command(self, command_name: str, args: Optional[dict] = None, display_message: str = "", storage_message: bool = True) -> bool:
|
||||||
elif action == "del":
|
"""发送命令消息"""
|
||||||
await self.send_text(f"删除用户:{username}")
|
|
||||||
elif action == "info":
|
async def send_emoji(self, emoji_base64: str) -> bool:
|
||||||
await self.send_text(f"用户信息:{username}")
|
"""发送表情包"""
|
||||||
|
|
||||||
return True, f"执行了{action}操作"
|
async def send_image(self, image_base64: str) -> bool:
|
||||||
|
"""发送图片"""
|
||||||
```
|
```
|
||||||
|
具体参数与用法参见`BaseCommand`基类的定义。
|
||||||
### 可选参数
|
|
||||||
|
|
||||||
```python
|
|
||||||
class HelpCommand(BaseCommand):
|
|
||||||
# 匹配 /help 或 /help topic
|
|
||||||
command_pattern = r"^/help(?:\s+(?P<topic>\w+))?$"
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
topic = self.matched_groups.get("topic")
|
|
||||||
|
|
||||||
if topic:
|
|
||||||
await self.send_text(f"显示{topic}的帮助")
|
|
||||||
else:
|
|
||||||
await self.send_text("显示总体帮助")
|
|
||||||
|
|
||||||
return True, "显示了帮助信息"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛑 拦截控制详解
|
|
||||||
|
|
||||||
### 拦截消息 (intercept_message = True)
|
|
||||||
|
|
||||||
```python
|
|
||||||
class AdminCommand(BaseCommand):
|
|
||||||
command_pattern = r"^/admin\s+.+"
|
|
||||||
command_help = "管理员命令"
|
|
||||||
intercept_message = True # 拦截,不继续处理
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
# 执行管理操作
|
|
||||||
await self.send_text("执行管理命令")
|
|
||||||
# 消息不会继续传递给其他组件
|
|
||||||
return True, "管理命令执行完成"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 不拦截消息 (intercept_message = False)
|
|
||||||
|
|
||||||
```python
|
|
||||||
class LogCommand(BaseCommand):
|
|
||||||
command_pattern = r"^/log\s+.+"
|
|
||||||
command_help = "记录日志"
|
|
||||||
intercept_message = False # 不拦截,继续处理
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
# 记录日志但不阻止后续处理
|
|
||||||
await self.send_text("已记录到日志")
|
|
||||||
# 消息会继续传递,可能触发Action等其他组件
|
|
||||||
return True, "日志记录完成"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 拦截控制的用途
|
|
||||||
|
|
||||||
| 场景 | intercept_message | 说明 |
|
|
||||||
| -------- | ----------------- | -------------------------- |
|
|
||||||
| 系统命令 | True | 防止命令被当作普通消息处理 |
|
|
||||||
| 查询命令 | True | 直接返回结果,无需后续处理 |
|
|
||||||
| 日志命令 | False | 记录但允许消息继续流转 |
|
|
||||||
| 监控命令 | False | 监控但不影响正常聊天 |
|
|
||||||
|
|
||||||
## 🎨 完整Command示例
|
|
||||||
|
|
||||||
### 用户管理Command
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system import BaseCommand
|
|
||||||
from typing import Tuple, Optional
|
|
||||||
|
|
||||||
class UserManagementCommand(BaseCommand):
|
|
||||||
"""用户管理Command - 展示复杂参数处理"""
|
|
||||||
|
|
||||||
command_pattern = r"^/user\s+(?P<action>add|del|list|info)\s*(?P<username>\w+)?(?:\s+--(?P<options>.+))?$"
|
|
||||||
command_help = "用户管理命令,支持添加、删除、列表、信息查询"
|
|
||||||
command_examples = [
|
|
||||||
"/user add 张三",
|
|
||||||
"/user del 李四",
|
|
||||||
"/user list",
|
|
||||||
"/user info 王五",
|
|
||||||
"/user add 赵六 --role=admin"
|
|
||||||
]
|
|
||||||
intercept_message = True
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
"""执行用户管理命令"""
|
|
||||||
try:
|
|
||||||
action = self.matched_groups.get("action")
|
|
||||||
username = self.matched_groups.get("username")
|
|
||||||
options = self.matched_groups.get("options")
|
|
||||||
|
|
||||||
# 解析选项
|
|
||||||
parsed_options = self._parse_options(options) if options else {}
|
|
||||||
|
|
||||||
if action == "add":
|
|
||||||
return await self._add_user(username, parsed_options)
|
|
||||||
elif action == "del":
|
|
||||||
return await self._delete_user(username)
|
|
||||||
elif action == "list":
|
|
||||||
return await self._list_users()
|
|
||||||
elif action == "info":
|
|
||||||
return await self._show_user_info(username)
|
|
||||||
else:
|
|
||||||
await self.send_text("❌ 不支持的操作")
|
|
||||||
return False, f"不支持的操作: {action}"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
await self.send_text(f"❌ 命令执行失败: {str(e)}")
|
|
||||||
return False, f"执行失败: {e}"
|
|
||||||
|
|
||||||
def _parse_options(self, options_str: str) -> dict:
|
|
||||||
"""解析命令选项"""
|
|
||||||
options = {}
|
|
||||||
if options_str:
|
|
||||||
for opt in options_str.split():
|
|
||||||
if "=" in opt:
|
|
||||||
key, value = opt.split("=", 1)
|
|
||||||
options[key] = value
|
|
||||||
return options
|
|
||||||
|
|
||||||
async def _add_user(self, username: str, options: dict) -> Tuple[bool, str]:
|
|
||||||
"""添加用户"""
|
|
||||||
if not username:
|
|
||||||
await self.send_text("❌ 请指定用户名")
|
|
||||||
return False, "缺少用户名参数"
|
|
||||||
|
|
||||||
# 检查用户是否已存在
|
|
||||||
existing_users = await self._get_user_list()
|
|
||||||
if username in existing_users:
|
|
||||||
await self.send_text(f"❌ 用户 {username} 已存在")
|
|
||||||
return False, f"用户已存在: {username}"
|
|
||||||
|
|
||||||
# 添加用户逻辑
|
|
||||||
role = options.get("role", "user")
|
|
||||||
await self.send_text(f"✅ 成功添加用户 {username},角色: {role}")
|
|
||||||
return True, f"添加用户成功: {username}"
|
|
||||||
|
|
||||||
async def _delete_user(self, username: str) -> Tuple[bool, str]:
|
|
||||||
"""删除用户"""
|
|
||||||
if not username:
|
|
||||||
await self.send_text("❌ 请指定用户名")
|
|
||||||
return False, "缺少用户名参数"
|
|
||||||
|
|
||||||
await self.send_text(f"✅ 用户 {username} 已删除")
|
|
||||||
return True, f"删除用户成功: {username}"
|
|
||||||
|
|
||||||
async def _list_users(self) -> Tuple[bool, str]:
|
|
||||||
"""列出所有用户"""
|
|
||||||
users = await self._get_user_list()
|
|
||||||
if users:
|
|
||||||
user_list = "\n".join([f"• {user}" for user in users])
|
|
||||||
await self.send_text(f"📋 用户列表:\n{user_list}")
|
|
||||||
else:
|
|
||||||
await self.send_text("📋 暂无用户")
|
|
||||||
return True, "显示用户列表"
|
|
||||||
|
|
||||||
async def _show_user_info(self, username: str) -> Tuple[bool, str]:
|
|
||||||
"""显示用户信息"""
|
|
||||||
if not username:
|
|
||||||
await self.send_text("❌ 请指定用户名")
|
|
||||||
return False, "缺少用户名参数"
|
|
||||||
|
|
||||||
# 模拟用户信息
|
|
||||||
user_info = f"""
|
|
||||||
👤 用户信息: {username}
|
|
||||||
📧 邮箱: {username}@example.com
|
|
||||||
🕒 注册时间: 2024-01-01
|
|
||||||
🎯 角色: 普通用户
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
await self.send_text(user_info)
|
|
||||||
return True, f"显示用户信息: {username}"
|
|
||||||
|
|
||||||
async def _get_user_list(self) -> list:
|
|
||||||
"""获取用户列表(示例)"""
|
|
||||||
return ["张三", "李四", "王五"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 系统信息Command
|
|
||||||
|
|
||||||
```python
|
|
||||||
class SystemInfoCommand(BaseCommand):
|
|
||||||
"""系统信息Command - 展示系统查询功能"""
|
|
||||||
|
|
||||||
command_pattern = r"^/(?:status|info)(?:\s+(?P<type>system|memory|plugins|all))?$"
|
|
||||||
command_help = "查询系统状态信息"
|
|
||||||
command_examples = [
|
|
||||||
"/status",
|
|
||||||
"/info system",
|
|
||||||
"/status memory",
|
|
||||||
"/info plugins"
|
|
||||||
]
|
|
||||||
intercept_message = True
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
"""执行系统信息查询"""
|
|
||||||
info_type = self.matched_groups.get("type", "all")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if info_type in ["system", "all"]:
|
|
||||||
await self._show_system_info()
|
|
||||||
|
|
||||||
if info_type in ["memory", "all"]:
|
|
||||||
await self._show_memory_info()
|
|
||||||
|
|
||||||
if info_type in ["plugins", "all"]:
|
|
||||||
await self._show_plugin_info()
|
|
||||||
|
|
||||||
return True, f"显示了{info_type}类型的系统信息"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
await self.send_text(f"❌ 获取系统信息失败: {str(e)}")
|
|
||||||
return False, f"查询失败: {e}"
|
|
||||||
|
|
||||||
async def _show_system_info(self):
|
|
||||||
"""显示系统信息"""
|
|
||||||
import platform
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
system_info = f"""
|
|
||||||
🖥️ **系统信息**
|
|
||||||
📱 平台: {platform.system()} {platform.release()}
|
|
||||||
🐍 Python: {platform.python_version()}
|
|
||||||
⏰ 运行时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
await self.send_text(system_info)
|
|
||||||
|
|
||||||
async def _show_memory_info(self):
|
|
||||||
"""显示内存信息"""
|
|
||||||
import psutil
|
|
||||||
|
|
||||||
memory = psutil.virtual_memory()
|
|
||||||
memory_info = f"""
|
|
||||||
💾 **内存信息**
|
|
||||||
📊 总内存: {memory.total // (1024**3)} GB
|
|
||||||
🟢 可用内存: {memory.available // (1024**3)} GB
|
|
||||||
📈 使用率: {memory.percent}%
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
await self.send_text(memory_info)
|
|
||||||
|
|
||||||
async def _show_plugin_info(self):
|
|
||||||
"""显示插件信息"""
|
|
||||||
# 通过配置获取插件信息
|
|
||||||
plugins = await self._get_loaded_plugins()
|
|
||||||
|
|
||||||
plugin_info = f"""
|
|
||||||
🔌 **插件信息**
|
|
||||||
📦 已加载插件: {len(plugins)}
|
|
||||||
🔧 活跃插件: {len([p for p in plugins if p.get('active', False)])}
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
await self.send_text(plugin_info)
|
|
||||||
|
|
||||||
async def _get_loaded_plugins(self) -> list:
|
|
||||||
"""获取已加载的插件列表"""
|
|
||||||
# 这里可以通过配置或API获取实际的插件信息
|
|
||||||
return [
|
|
||||||
{"name": "core_actions", "active": True},
|
|
||||||
{"name": "example_plugin", "active": True},
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 自定义前缀Command
|
|
||||||
|
|
||||||
```python
|
|
||||||
class CustomPrefixCommand(BaseCommand):
|
|
||||||
"""自定义前缀Command - 展示非/前缀的命令"""
|
|
||||||
|
|
||||||
# 使用!前缀而不是/前缀
|
|
||||||
command_pattern = r"^[!!](?P<command>roll|dice)\s*(?P<count>\d+)?$"
|
|
||||||
command_help = "骰子命令,使用!前缀"
|
|
||||||
command_examples = ["!roll", "!dice 6", "!roll 20"]
|
|
||||||
intercept_message = True
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
"""执行骰子命令"""
|
|
||||||
import random
|
|
||||||
|
|
||||||
command = self.matched_groups.get("command")
|
|
||||||
count = int(self.matched_groups.get("count", "6"))
|
|
||||||
|
|
||||||
# 限制骰子面数
|
|
||||||
if count > 100:
|
|
||||||
await self.send_text("❌ 骰子面数不能超过100")
|
|
||||||
return False, "骰子面数超限"
|
|
||||||
|
|
||||||
result = random.randint(1, count)
|
|
||||||
await self.send_text(f"🎲 投掷{count}面骰子,结果: {result}")
|
|
||||||
|
|
||||||
return True, f"投掷了{count}面骰子,结果{result}"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 性能优化建议
|
|
||||||
|
|
||||||
### 1. 正则表达式优化
|
|
||||||
|
|
||||||
```python
|
|
||||||
# ✅ 好的做法 - 简单直接
|
|
||||||
command_pattern = r"^/ping$"
|
|
||||||
|
|
||||||
# ❌ 避免 - 过于复杂
|
|
||||||
command_pattern = r"^/(?:ping|pong|test|check|status|info|help|...)"
|
|
||||||
|
|
||||||
# ✅ 好的做法 - 分离复杂逻辑
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 参数验证
|
|
||||||
|
|
||||||
```python
|
|
||||||
# ✅ 好的做法 - 早期验证
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
username = self.matched_groups.get("username")
|
|
||||||
if not username:
|
|
||||||
await self.send_text("❌ 请提供用户名")
|
|
||||||
return False, "缺少参数"
|
|
||||||
|
|
||||||
# 继续处理...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 错误处理
|
|
||||||
|
|
||||||
```python
|
|
||||||
# ✅ 好的做法 - 完整错误处理
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
try:
|
|
||||||
# 主要逻辑
|
|
||||||
result = await self._process_command()
|
|
||||||
return True, "执行成功"
|
|
||||||
except ValueError as e:
|
|
||||||
await self.send_text(f"❌ 参数错误: {e}")
|
|
||||||
return False, f"参数错误: {e}"
|
|
||||||
except Exception as e:
|
|
||||||
await self.send_text(f"❌ 执行失败: {e}")
|
|
||||||
return False, f"执行失败: {e}"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 最佳实践
|
|
||||||
|
|
||||||
### 1. 命令设计原则
|
|
||||||
|
|
||||||
```python
|
|
||||||
# ✅ 好的命令设计
|
|
||||||
"/user add 张三" # 动作 + 对象 + 参数
|
|
||||||
"/config set key=value" # 动作 + 子动作 + 参数
|
|
||||||
"/help command" # 动作 + 可选参数
|
|
||||||
|
|
||||||
# ❌ 避免的设计
|
|
||||||
"/add_user_with_name_张三" # 过于冗长
|
|
||||||
"/u a 张三" # 过于简写
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 帮助信息
|
|
||||||
|
|
||||||
```python
|
|
||||||
class WellDocumentedCommand(BaseCommand):
|
|
||||||
command_pattern = r"^/example\s+(?P<param>\w+)$"
|
|
||||||
command_help = "示例命令:处理指定参数并返回结果"
|
|
||||||
command_examples = [
|
|
||||||
"/example test",
|
|
||||||
"/example debug",
|
|
||||||
"/example production"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 错误处理
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
param = self.matched_groups.get("param")
|
|
||||||
|
|
||||||
# 参数验证
|
|
||||||
if param not in ["test", "debug", "production"]:
|
|
||||||
await self.send_text("❌ 无效的参数,支持: test, debug, production")
|
|
||||||
return False, "无效参数"
|
|
||||||
|
|
||||||
# 执行逻辑
|
|
||||||
try:
|
|
||||||
result = await self._process_param(param)
|
|
||||||
await self.send_text(f"✅ 处理完成: {result}")
|
|
||||||
return True, f"处理{param}成功"
|
|
||||||
except Exception as e:
|
|
||||||
await self.send_text("❌ 处理失败,请稍后重试")
|
|
||||||
return False, f"处理失败: {e}"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 配置集成
|
|
||||||
|
|
||||||
```python
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
# 从配置读取设置
|
|
||||||
max_items = self.get_config("command.max_items", 10)
|
|
||||||
timeout = self.get_config("command.timeout", 30)
|
|
||||||
|
|
||||||
# 使用配置进行处理
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Command vs Action 选择指南
|
|
||||||
|
|
||||||
### 使用Command的场景
|
|
||||||
|
|
||||||
- ✅ 用户需要明确调用特定功能
|
|
||||||
- ✅ 需要精确的参数控制
|
|
||||||
- ✅ 管理和配置操作
|
|
||||||
- ✅ 查询和信息显示
|
|
||||||
- ✅ 系统维护命令
|
|
||||||
|
|
||||||
### 使用Action的场景
|
|
||||||
|
|
||||||
- ✅ 增强麦麦的智能行为
|
|
||||||
- ✅ 根据上下文自动触发
|
|
||||||
- ✅ 情绪和表情表达
|
|
||||||
- ✅ 智能建议和帮助
|
|
||||||
- ✅ 随机化的互动
|
|
||||||
|
|
||||||
|
|
||||||
@@ -6,34 +6,6 @@
|
|||||||
>
|
>
|
||||||
> 系统会根据你在代码中定义的 `config_schema` 自动生成配置文件。手动创建配置文件会破坏自动化流程,导致配置不一致、缺失注释和文档等问题。
|
> 系统会根据你在代码中定义的 `config_schema` 自动生成配置文件。手动创建配置文件会破坏自动化流程,导致配置不一致、缺失注释和文档等问题。
|
||||||
|
|
||||||
## 📖 目录
|
|
||||||
|
|
||||||
1. [配置架构变更说明](#配置架构变更说明)
|
|
||||||
2. [配置版本管理](#配置版本管理)
|
|
||||||
3. [配置定义:Schema驱动的配置系统](#配置定义schema驱动的配置系统)
|
|
||||||
4. [配置访问:在Action和Command中使用配置](#配置访问在action和command中使用配置)
|
|
||||||
5. [完整示例:从定义到使用](#完整示例从定义到使用)
|
|
||||||
6. [最佳实践与注意事项](#最佳实践与注意事项)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置架构变更说明
|
|
||||||
|
|
||||||
- **`_manifest.json`** - 负责插件的**元数据信息**(静态)
|
|
||||||
- 插件名称、版本、描述
|
|
||||||
- 作者信息、许可证
|
|
||||||
- 仓库链接、关键词、分类
|
|
||||||
- 组件列表、兼容性信息
|
|
||||||
|
|
||||||
- **`config.toml`** - 负责插件的**运行时配置**(动态)
|
|
||||||
- `enabled` - 是否启用插件
|
|
||||||
- 功能参数配置
|
|
||||||
- 组件启用开关
|
|
||||||
- 用户可调整的行为参数
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置版本管理
|
## 配置版本管理
|
||||||
|
|
||||||
### 🎯 版本管理概述
|
### 🎯 版本管理概述
|
||||||
@@ -103,7 +75,7 @@ config_schema = {
|
|||||||
2. **迁移配置值** - 将旧配置文件中的值迁移到新结构中
|
2. **迁移配置值** - 将旧配置文件中的值迁移到新结构中
|
||||||
3. **处理新增字段** - 新增的配置项使用默认值
|
3. **处理新增字段** - 新增的配置项使用默认值
|
||||||
4. **更新版本号** - `config_version` 字段自动更新为最新版本
|
4. **更新版本号** - `config_version` 字段自动更新为最新版本
|
||||||
5. **保存配置文件** - 迁移后的配置直接覆盖原文件(不保留备份)
|
5. **保存配置文件** - 迁移后的配置直接覆盖原文件**(不保留备份)**
|
||||||
|
|
||||||
### 🔧 实际使用示例
|
### 🔧 实际使用示例
|
||||||
|
|
||||||
@@ -174,28 +146,13 @@ min_duration = 120
|
|||||||
- 跳过版本检查和迁移
|
- 跳过版本检查和迁移
|
||||||
- 直接加载现有配置
|
- 直接加载现有配置
|
||||||
- 新增的配置项在代码中使用默认值访问
|
- 新增的配置项在代码中使用默认值访问
|
||||||
|
- 系统会详细记录配置迁移过程。
|
||||||
### 📝 配置迁移日志
|
|
||||||
|
|
||||||
系统会详细记录配置迁移过程:
|
|
||||||
|
|
||||||
```log
|
|
||||||
[MutePlugin] 检测到配置版本需要更新: 当前=v1.0.0, 期望=v1.1.0
|
|
||||||
[MutePlugin] 生成新配置结构...
|
|
||||||
[MutePlugin] 迁移配置值: plugin.enabled = true
|
|
||||||
[MutePlugin] 更新配置版本: plugin.config_version = 1.1.0 (旧值: 1.0.0)
|
|
||||||
[MutePlugin] 迁移配置值: mute.min_duration = 120
|
|
||||||
[MutePlugin] 迁移配置值: mute.max_duration = 3600
|
|
||||||
[MutePlugin] 新增节: permissions
|
|
||||||
[MutePlugin] 配置文件已从 v1.0.0 更新到 v1.1.0
|
|
||||||
```
|
|
||||||
|
|
||||||
### ⚠️ 重要注意事项
|
### ⚠️ 重要注意事项
|
||||||
|
|
||||||
#### 1. 版本号管理
|
#### 1. 版本号管理
|
||||||
- 当你修改 `config_schema` 时,**必须同步更新** `config_version`
|
- 当你修改 `config_schema` 时,**必须同步更新** `config_version`
|
||||||
- 建议使用语义化版本号 (例如:`1.0.0`, `1.1.0`, `2.0.0`)
|
- 请使用语义化版本号 (例如:`1.0.0`, `1.1.0`, `2.0.0`)
|
||||||
- 配置结构的重大变更应该增加主版本号
|
|
||||||
|
|
||||||
#### 2. 迁移策略
|
#### 2. 迁移策略
|
||||||
- **保留原值优先**: 迁移时优先保留用户的原有配置值
|
- **保留原值优先**: 迁移时优先保留用户的原有配置值
|
||||||
@@ -207,45 +164,7 @@ min_duration = 120
|
|||||||
- **不保留备份**: 迁移后直接覆盖原配置文件,不保留备份
|
- **不保留备份**: 迁移后直接覆盖原配置文件,不保留备份
|
||||||
- **失败安全**: 如果迁移过程中出现错误,会回退到原配置
|
- **失败安全**: 如果迁移过程中出现错误,会回退到原配置
|
||||||
|
|
||||||
---
|
## 配置定义
|
||||||
|
|
||||||
## 配置定义:Schema驱动的配置系统
|
|
||||||
|
|
||||||
### 核心理念:Schema驱动的配置
|
|
||||||
|
|
||||||
在新版插件系统中,我们引入了一套 **配置Schema(模式)驱动** 的机制。**你不需要也不应该手动创建和维护 `config.toml` 文件**,而是通过在插件代码中 **声明配置的结构**,系统将为你完成剩下的工作。
|
|
||||||
|
|
||||||
> **⚠️ 绝对不要手动创建 config.toml 文件!**
|
|
||||||
>
|
|
||||||
> - ❌ **错误做法**:手动在插件目录下创建 `config.toml` 文件
|
|
||||||
> - ✅ **正确做法**:在插件代码中定义 `config_schema`,让系统自动生成配置文件
|
|
||||||
|
|
||||||
**核心优势:**
|
|
||||||
|
|
||||||
- **自动化 (Automation)**: 如果配置文件不存在,系统会根据你的声明 **自动生成** 一份包含默认值和详细注释的 `config.toml` 文件。
|
|
||||||
- **规范化 (Standardization)**: 所有插件的配置都遵循统一的结构,提升了可维护性。
|
|
||||||
- **自带文档 (Self-documenting)**: 配置文件中的每一项都包含详细的注释、类型说明、可选值和示例,极大地降低了用户的使用门槛。
|
|
||||||
- **健壮性 (Robustness)**: 在代码中直接定义配置的类型和默认值,减少了因配置错误导致的运行时问题。
|
|
||||||
- **易于管理 (Easy Management)**: 生成的配置文件可以方便地加入 `.gitignore`,避免将个人配置(如API Key)提交到版本库。
|
|
||||||
|
|
||||||
### 配置生成工作流程
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
A[编写插件代码] --> B[定义 config_schema]
|
|
||||||
B --> C[首次加载插件]
|
|
||||||
C --> D{config.toml 是否存在?}
|
|
||||||
D -->|不存在| E[系统自动生成 config.toml]
|
|
||||||
D -->|存在| F[加载现有配置文件]
|
|
||||||
E --> G[配置完成,插件可用]
|
|
||||||
F --> G
|
|
||||||
|
|
||||||
style E fill:#90EE90
|
|
||||||
style B fill:#87CEEB
|
|
||||||
style G fill:#DDA0DD
|
|
||||||
```
|
|
||||||
|
|
||||||
### 如何定义配置
|
|
||||||
|
|
||||||
配置的定义在你的插件主类(继承自 `BasePlugin`)中完成,主要通过两个类属性:
|
配置的定义在你的插件主类(继承自 `BasePlugin`)中完成,主要通过两个类属性:
|
||||||
|
|
||||||
@@ -257,6 +176,7 @@ graph TD
|
|||||||
每个配置项都通过一个 `ConfigField` 对象来定义。
|
每个配置项都通过一个 `ConfigField` 对象来定义。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
from src.plugin_system.base.config_types import ConfigField
|
from src.plugin_system.base.config_types import ConfigField
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -270,28 +190,21 @@ class ConfigField:
|
|||||||
choices: Optional[List[Any]] = None # 可选值列表 (可选)
|
choices: Optional[List[Any]] = None # 可选值列表 (可选)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 配置定义示例
|
### 配置示例
|
||||||
|
|
||||||
让我们以一个功能丰富的 `MutePlugin` 为例,看看如何定义它的配置。
|
让我们以一个功能丰富的 `MutePlugin` 为例,看看如何定义它的配置。
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# src/plugins/built_in/mute_plugin/plugin.py
|
# src/plugins/built_in/mute_plugin/plugin.py
|
||||||
|
|
||||||
from src.plugin_system import BasePlugin, register_plugin
|
from src.plugin_system import BasePlugin, register_plugin, ConfigField
|
||||||
from src.plugin_system.base.config_types import ConfigField
|
|
||||||
from typing import List, Tuple, Type
|
from typing import List, Tuple, Type
|
||||||
|
|
||||||
@register_plugin
|
@register_plugin
|
||||||
class MutePlugin(BasePlugin):
|
class MutePlugin(BasePlugin):
|
||||||
"""禁言插件"""
|
"""禁言插件"""
|
||||||
|
|
||||||
# 插件基本信息
|
# 这里是插件基本信息,略去
|
||||||
plugin_name = "mute_plugin"
|
|
||||||
plugin_description = "群聊禁言管理插件,提供智能禁言功能"
|
|
||||||
plugin_version = "2.0.0"
|
|
||||||
plugin_author = "MaiBot开发团队"
|
|
||||||
enable_plugin = True
|
|
||||||
config_file_name = "config.toml"
|
|
||||||
|
|
||||||
# 步骤1: 定义配置节的描述
|
# 步骤1: 定义配置节的描述
|
||||||
config_section_descriptions = {
|
config_section_descriptions = {
|
||||||
@@ -339,22 +252,9 @@ class MutePlugin(BasePlugin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
# 这里是插件方法,略去
|
||||||
# 在这里可以通过 self.get_config() 来获取配置值
|
|
||||||
enable_smart_mute = self.get_config("components.enable_smart_mute", True)
|
|
||||||
enable_mute_command = self.get_config("components.enable_mute_command", False)
|
|
||||||
|
|
||||||
components = []
|
|
||||||
if enable_smart_mute:
|
|
||||||
components.append((SmartMuteAction.get_action_info(), SmartMuteAction))
|
|
||||||
if enable_mute_command:
|
|
||||||
components.append((MuteCommand.get_command_info(), MuteCommand))
|
|
||||||
|
|
||||||
return components
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 自动生成的配置文件
|
|
||||||
|
|
||||||
当 `mute_plugin` 首次加载且其目录中不存在 `config.toml` 时,系统会自动创建以下文件:
|
当 `mute_plugin` 首次加载且其目录中不存在 `config.toml` 时,系统会自动创建以下文件:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
@@ -413,317 +313,24 @@ prefix = "[MutePlugin]"
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 配置访问:在Action和Command中使用配置
|
## 配置访问
|
||||||
|
|
||||||
### 问题描述
|
如果你想要在你的组件中访问配置,可以通过组件内置的 `get_config()` 方法访问配置。
|
||||||
|
|
||||||
在插件开发中,你可能遇到这样的问题:
|
其参数为一个命名空间化的字符串。以上面的 `MutePlugin` 为例,你可以这样访问配置:
|
||||||
- 想要在Action或Command中访问插件配置
|
|
||||||
|
|
||||||
### ✅ 解决方案
|
|
||||||
|
|
||||||
**直接使用 `self.get_config()` 方法!**
|
|
||||||
|
|
||||||
系统已经自动为你处理了配置传递,你只需要通过组件内置的 `get_config` 方法访问配置即可。
|
|
||||||
|
|
||||||
### 📖 快速示例
|
|
||||||
|
|
||||||
#### 在Action中访问配置
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system import BaseAction
|
enable_smart_mute = self.get_config("components.enable_smart_mute", True)
|
||||||
|
|
||||||
class MyAction(BaseAction):
|
|
||||||
async def execute(self):
|
|
||||||
# 方法1: 获取配置值(带默认值)
|
|
||||||
api_key = self.get_config("api.key", "default_key")
|
|
||||||
timeout = self.get_config("api.timeout", 30)
|
|
||||||
|
|
||||||
# 方法2: 支持嵌套键访问
|
|
||||||
log_level = self.get_config("advanced.logging.level", "INFO")
|
|
||||||
|
|
||||||
# 方法3: 直接访问顶层配置
|
|
||||||
enable_feature = self.get_config("features.enable_smart", False)
|
|
||||||
|
|
||||||
# 使用配置值
|
|
||||||
if enable_feature:
|
|
||||||
await self.send_text(f"API密钥: {api_key}")
|
|
||||||
|
|
||||||
return True, "配置访问成功"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 在Command中访问配置
|
如果尝试访问了一个不存在的配置项,系统会自动返回默认值(你传递的)或者 `None`。
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system import BaseCommand
|
|
||||||
|
|
||||||
class MyCommand(BaseCommand):
|
|
||||||
async def execute(self):
|
|
||||||
# 使用方式与Action完全相同
|
|
||||||
welcome_msg = self.get_config("messages.welcome", "欢迎!")
|
|
||||||
max_results = self.get_config("search.max_results", 10)
|
|
||||||
|
|
||||||
# 根据配置执行不同逻辑
|
|
||||||
if self.get_config("features.debug_mode", False):
|
|
||||||
await self.send_text(f"调试模式已启用,最大结果数: {max_results}")
|
|
||||||
|
|
||||||
await self.send_text(welcome_msg)
|
|
||||||
return True, "命令执行完成"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔧 API方法详解
|
|
||||||
|
|
||||||
#### 1. `get_config(key, default=None)`
|
|
||||||
|
|
||||||
获取配置值,支持嵌套键访问:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 简单键
|
|
||||||
value = self.get_config("timeout", 30)
|
|
||||||
|
|
||||||
# 嵌套键(用点号分隔)
|
|
||||||
value = self.get_config("database.connection.host", "localhost")
|
|
||||||
value = self.get_config("features.ai.model", "gpt-3.5-turbo")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 类型安全的配置访问
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 确保正确的类型
|
|
||||||
max_retries = self.get_config("api.max_retries", 3)
|
|
||||||
if not isinstance(max_retries, int):
|
|
||||||
max_retries = 3 # 使用安全的默认值
|
|
||||||
|
|
||||||
# 布尔值配置
|
|
||||||
debug_mode = self.get_config("features.debug_mode", False)
|
|
||||||
if debug_mode:
|
|
||||||
# 调试功能逻辑
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 配置驱动的组件行为
|
|
||||||
|
|
||||||
```python
|
|
||||||
class ConfigDrivenAction(BaseAction):
|
|
||||||
async def execute(self):
|
|
||||||
# 根据配置决定激活行为
|
|
||||||
activation_config = {
|
|
||||||
"use_keywords": self.get_config("activation.use_keywords", True),
|
|
||||||
"use_llm": self.get_config("activation.use_llm", False),
|
|
||||||
"keywords": self.get_config("activation.keywords", []),
|
|
||||||
}
|
|
||||||
|
|
||||||
# 根据配置调整功能
|
|
||||||
features = {
|
|
||||||
"enable_emoji": self.get_config("features.enable_emoji", True),
|
|
||||||
"enable_llm_reply": self.get_config("features.enable_llm_reply", False),
|
|
||||||
"max_length": self.get_config("output.max_length", 200),
|
|
||||||
}
|
|
||||||
|
|
||||||
# 使用配置执行逻辑
|
|
||||||
if features["enable_llm_reply"]:
|
|
||||||
# 使用LLM生成回复
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# 使用模板回复
|
|
||||||
pass
|
|
||||||
|
|
||||||
return True, "配置驱动执行完成"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔄 配置传递机制
|
|
||||||
|
|
||||||
系统自动处理配置传递,无需手动操作:
|
|
||||||
|
|
||||||
1. **插件初始化** → `BasePlugin`加载`config.toml`到`self.config`
|
|
||||||
2. **组件注册** → 系统记录插件配置
|
|
||||||
3. **组件实例化** → 自动传递`plugin_config`参数给Action/Command
|
|
||||||
4. **配置访问** → 组件通过`self.get_config()`直接访问配置
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 完整示例:从定义到使用
|
|
||||||
|
|
||||||
### 插件定义
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system.base.config_types import ConfigField
|
|
||||||
|
|
||||||
@register_plugin
|
|
||||||
class GreetingPlugin(BasePlugin):
|
|
||||||
"""问候插件完整示例"""
|
|
||||||
|
|
||||||
plugin_name = "greeting_plugin"
|
|
||||||
plugin_description = "智能问候插件,展示配置定义和访问的完整流程"
|
|
||||||
plugin_version = "1.0.0"
|
|
||||||
config_file_name = "config.toml"
|
|
||||||
|
|
||||||
# 配置节描述
|
|
||||||
config_section_descriptions = {
|
|
||||||
"plugin": "插件启用配置",
|
|
||||||
"greeting": "问候功能配置",
|
|
||||||
"features": "功能开关配置",
|
|
||||||
"messages": "消息模板配置"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 配置Schema定义
|
|
||||||
config_schema = {
|
|
||||||
"plugin": {
|
|
||||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件")
|
|
||||||
},
|
|
||||||
"greeting": {
|
|
||||||
"template": ConfigField(
|
|
||||||
type=str,
|
|
||||||
default="你好,{username}!欢迎使用问候插件!",
|
|
||||||
description="问候消息模板"
|
|
||||||
),
|
|
||||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
|
|
||||||
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成个性化问候")
|
|
||||||
},
|
|
||||||
"features": {
|
|
||||||
"smart_detection": ConfigField(type=bool, default=True, description="是否启用智能检测"),
|
|
||||||
"random_greeting": ConfigField(type=bool, default=False, description="是否使用随机问候语"),
|
|
||||||
"max_greetings_per_hour": ConfigField(type=int, default=5, description="每小时最大问候次数")
|
|
||||||
},
|
|
||||||
"messages": {
|
|
||||||
"custom_greetings": ConfigField(
|
|
||||||
type=list,
|
|
||||||
default=["你好!", "嗨!", "欢迎!"],
|
|
||||||
description="自定义问候语列表"
|
|
||||||
),
|
|
||||||
"error_message": ConfigField(
|
|
||||||
type=str,
|
|
||||||
default="问候功能暂时不可用",
|
|
||||||
description="错误时显示的消息"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
|
||||||
"""根据配置动态注册组件"""
|
|
||||||
components = []
|
|
||||||
|
|
||||||
# 根据配置决定是否注册组件
|
|
||||||
if self.get_config("plugin.enabled", True):
|
|
||||||
components.append((SmartGreetingAction.get_action_info(), SmartGreetingAction))
|
|
||||||
components.append((GreetingCommand.get_command_info(), GreetingCommand))
|
|
||||||
|
|
||||||
return components
|
|
||||||
```
|
|
||||||
|
|
||||||
### Action组件使用配置
|
|
||||||
|
|
||||||
```python
|
|
||||||
class SmartGreetingAction(BaseAction):
|
|
||||||
"""智能问候Action - 展示配置访问"""
|
|
||||||
|
|
||||||
focus_activation_type = ActionActivationType.KEYWORD
|
|
||||||
normal_activation_type = ActionActivationType.KEYWORD
|
|
||||||
activation_keywords = ["你好", "hello", "hi"]
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
|
||||||
"""执行智能问候,大量使用配置"""
|
|
||||||
try:
|
|
||||||
# 检查插件是否启用
|
|
||||||
if not self.get_config("plugin.enabled", True):
|
|
||||||
return False, "插件已禁用"
|
|
||||||
|
|
||||||
# 获取问候配置
|
|
||||||
template = self.get_config("greeting.template", "你好,{username}!")
|
|
||||||
enable_emoji = self.get_config("greeting.enable_emoji", True)
|
|
||||||
enable_llm = self.get_config("greeting.enable_llm", False)
|
|
||||||
|
|
||||||
# 获取功能配置
|
|
||||||
smart_detection = self.get_config("features.smart_detection", True)
|
|
||||||
random_greeting = self.get_config("features.random_greeting", False)
|
|
||||||
max_per_hour = self.get_config("features.max_greetings_per_hour", 5)
|
|
||||||
|
|
||||||
# 获取消息配置
|
|
||||||
custom_greetings = self.get_config("messages.custom_greetings", [])
|
|
||||||
error_message = self.get_config("messages.error_message", "问候功能不可用")
|
|
||||||
|
|
||||||
# 根据配置执行不同逻辑
|
|
||||||
username = self.action_data.get("username", "用户")
|
|
||||||
|
|
||||||
if random_greeting and custom_greetings:
|
|
||||||
# 使用随机自定义问候语
|
|
||||||
import random
|
|
||||||
greeting_msg = random.choice(custom_greetings)
|
|
||||||
elif enable_llm:
|
|
||||||
# 使用LLM生成个性化问候
|
|
||||||
greeting_msg = await self._generate_llm_greeting(username)
|
|
||||||
else:
|
|
||||||
# 使用模板问候
|
|
||||||
greeting_msg = template.format(username=username)
|
|
||||||
|
|
||||||
# 发送问候消息
|
|
||||||
await self.send_text(greeting_msg)
|
|
||||||
|
|
||||||
# 根据配置发送表情
|
|
||||||
if enable_emoji:
|
|
||||||
await self.send_emoji("😊")
|
|
||||||
|
|
||||||
return True, f"向{username}发送了问候"
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# 使用配置的错误消息
|
|
||||||
await self.send_text(self.get_config("messages.error_message", "出错了"))
|
|
||||||
return False, f"问候失败: {str(e)}"
|
|
||||||
|
|
||||||
async def _generate_llm_greeting(self, username: str) -> str:
|
|
||||||
"""根据配置使用LLM生成问候语"""
|
|
||||||
# 这里可以进一步使用配置来定制LLM行为
|
|
||||||
llm_style = self.get_config("greeting.llm_style", "friendly")
|
|
||||||
# ... LLM调用逻辑
|
|
||||||
return f"你好 {username}!很高兴见到你!"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command组件使用配置
|
|
||||||
|
|
||||||
```python
|
|
||||||
class GreetingCommand(BaseCommand):
|
|
||||||
"""问候命令 - 展示配置访问"""
|
|
||||||
|
|
||||||
command_pattern = r"^/greet(?:\s+(?P<username>\w+))?$"
|
|
||||||
command_help = "发送问候消息"
|
|
||||||
command_examples = ["/greet", "/greet Alice"]
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
"""执行问候命令"""
|
|
||||||
# 检查功能是否启用
|
|
||||||
if not self.get_config("plugin.enabled", True):
|
|
||||||
await self.send_text("问候功能已禁用")
|
|
||||||
return False, "功能禁用"
|
|
||||||
|
|
||||||
# 获取用户名
|
|
||||||
username = self.matched_groups.get("username", "用户")
|
|
||||||
|
|
||||||
# 根据配置选择问候方式
|
|
||||||
if self.get_config("features.random_greeting", False):
|
|
||||||
custom_greetings = self.get_config("messages.custom_greetings", ["你好!"])
|
|
||||||
import random
|
|
||||||
greeting = random.choice(custom_greetings)
|
|
||||||
else:
|
|
||||||
template = self.get_config("greeting.template", "你好,{username}!")
|
|
||||||
greeting = template.format(username=username)
|
|
||||||
|
|
||||||
# 发送问候
|
|
||||||
await self.send_text(greeting)
|
|
||||||
|
|
||||||
# 根据配置发送表情
|
|
||||||
if self.get_config("greeting.enable_emoji", True):
|
|
||||||
await self.send_text("😊")
|
|
||||||
|
|
||||||
return True, "问候发送成功"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 最佳实践与注意事项
|
## 最佳实践与注意事项
|
||||||
|
|
||||||
### 配置定义最佳实践
|
|
||||||
|
|
||||||
> **🚨 核心原则:永远不要手动创建 config.toml 文件!**
|
**🚨 核心原则:永远不要手动创建 config.toml 文件!**
|
||||||
|
|
||||||
1. **🔥 绝不手动创建配置文件**: **任何时候都不要手动创建 `config.toml` 文件**!必须通过在 `plugin.py` 中定义 `config_schema` 让系统自动生成。
|
1. **🔥 绝不手动创建配置文件**: **任何时候都不要手动创建 `config.toml` 文件**!必须通过在 `plugin.py` 中定义 `config_schema` 让系统自动生成。
|
||||||
- ❌ **禁止**:`touch config.toml`、手动编写配置文件
|
- ❌ **禁止**:`touch config.toml`、手动编写配置文件
|
||||||
@@ -737,76 +344,4 @@ class GreetingCommand(BaseCommand):
|
|||||||
|
|
||||||
5. **gitignore**: 将 `plugins/*/config.toml` 或 `src/plugins/built_in/*/config.toml` 加入 `.gitignore`,以避免提交个人敏感信息。
|
5. **gitignore**: 将 `plugins/*/config.toml` 或 `src/plugins/built_in/*/config.toml` 加入 `.gitignore`,以避免提交个人敏感信息。
|
||||||
|
|
||||||
6. **配置文件只供修改**: 自动生成的 `config.toml` 文件只应该被用户**修改**,而不是从零创建。
|
6. **配置文件只供修改**: 自动生成的 `config.toml` 文件只应该被用户**修改**,而不是从零创建。
|
||||||
|
|
||||||
### 配置访问最佳实践
|
|
||||||
|
|
||||||
#### 1. 总是提供默认值
|
|
||||||
|
|
||||||
```python
|
|
||||||
# ✅ 好的做法
|
|
||||||
timeout = self.get_config("api.timeout", 30)
|
|
||||||
|
|
||||||
# ❌ 避免这样做
|
|
||||||
timeout = self.get_config("api.timeout") # 可能返回None
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 验证配置类型
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 获取配置后验证类型
|
|
||||||
max_items = self.get_config("list.max_items", 10)
|
|
||||||
if not isinstance(max_items, int) or max_items <= 0:
|
|
||||||
max_items = 10 # 使用安全的默认值
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 缓存复杂配置解析
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MyAction(BaseAction):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
# 在初始化时解析复杂配置,避免重复解析
|
|
||||||
self._api_config = self._parse_api_config()
|
|
||||||
|
|
||||||
def _parse_api_config(self):
|
|
||||||
return {
|
|
||||||
'key': self.get_config("api.key", ""),
|
|
||||||
'timeout': self.get_config("api.timeout", 30),
|
|
||||||
'retries': self.get_config("api.max_retries", 3)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. 配置驱动的组件注册
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
|
||||||
"""根据配置动态注册组件"""
|
|
||||||
components = []
|
|
||||||
|
|
||||||
# 从配置获取组件启用状态
|
|
||||||
enable_action = self.get_config("components.enable_action", True)
|
|
||||||
enable_command = self.get_config("components.enable_command", True)
|
|
||||||
|
|
||||||
if enable_action:
|
|
||||||
components.append((MyAction.get_action_info(), MyAction))
|
|
||||||
if enable_command:
|
|
||||||
components.append((MyCommand.get_command_info(), MyCommand))
|
|
||||||
|
|
||||||
return components
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🎉 总结
|
|
||||||
|
|
||||||
现在你掌握了插件配置的完整流程:
|
|
||||||
|
|
||||||
1. **定义配置**: 在插件中使用 `config_schema` 定义配置结构
|
|
||||||
2. **访问配置**: 在组件中使用 `self.get_config("key", default_value)` 访问配置
|
|
||||||
3. **自动生成**: 系统自动生成带注释的配置文件
|
|
||||||
4. **动态行为**: 根据配置动态调整插件行为
|
|
||||||
|
|
||||||
> **🚨 最后强调:任何时候都不要手动创建 config.toml 文件!**
|
|
||||||
>
|
|
||||||
> 让系统根据你的 `config_schema` 自动生成配置文件,这是插件系统的核心设计原则。
|
|
||||||
|
|
||||||
不需要继承`BasePlugin`,不需要复杂的配置传递,不需要手动创建配置文件,组件内置的`get_config`方法和自动化的配置生成机制已经为你准备好了一切!
|
|
||||||
@@ -1,93 +1,6 @@
|
|||||||
# 📦 插件依赖管理系统
|
# 📦 插件依赖管理系统
|
||||||
|
|
||||||
> 🎯 **简介**:MaiBot插件系统提供了强大的Python包依赖管理功能,让插件开发更加便捷和可靠。
|
现在的Python依赖包管理依然存在问题,请保留你的`python_dependencies`属性,等待后续重构。
|
||||||
|
|
||||||
## ✨ 功能概述
|
|
||||||
|
|
||||||
### 🎯 核心能力
|
|
||||||
- **声明式依赖**:插件可以明确声明需要的Python包
|
|
||||||
- **智能检查**:自动检查依赖包的安装状态
|
|
||||||
- **版本控制**:精确的版本要求管理
|
|
||||||
- **可选依赖**:区分必需依赖和可选依赖
|
|
||||||
- **自动安装**:可选的自动安装功能
|
|
||||||
- **批量管理**:生成统一的requirements文件
|
|
||||||
- **安全控制**:防止意外安装和版本冲突
|
|
||||||
|
|
||||||
### 🔄 工作流程
|
|
||||||
1. **声明依赖** → 在插件中声明所需的Python包
|
|
||||||
2. **加载检查** → 插件加载时自动检查依赖状态
|
|
||||||
3. **状态报告** → 详细报告缺失或版本不匹配的依赖
|
|
||||||
4. **智能安装** → 可选择自动安装或手动安装
|
|
||||||
5. **运行时处理** → 插件运行时优雅处理依赖缺失
|
|
||||||
|
|
||||||
## 🚀 快速开始
|
|
||||||
|
|
||||||
### 步骤1:声明依赖
|
|
||||||
|
|
||||||
在你的插件类中添加`python_dependencies`字段:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system import BasePlugin, PythonDependency, register_plugin
|
|
||||||
|
|
||||||
@register_plugin
|
|
||||||
class MyPlugin(BasePlugin):
|
|
||||||
name = "my_plugin"
|
|
||||||
|
|
||||||
# 声明Python包依赖
|
|
||||||
python_dependencies = [
|
|
||||||
PythonDependency(
|
|
||||||
package_name="requests",
|
|
||||||
version=">=2.25.0",
|
|
||||||
description="HTTP请求库,用于网络通信"
|
|
||||||
),
|
|
||||||
PythonDependency(
|
|
||||||
package_name="numpy",
|
|
||||||
version=">=1.20.0",
|
|
||||||
optional=True,
|
|
||||||
description="数值计算库(可选功能)"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_plugin_components(self):
|
|
||||||
# 返回插件组件
|
|
||||||
return []
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤2:处理依赖
|
|
||||||
|
|
||||||
在组件代码中优雅处理依赖缺失:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class MyAction(BaseAction):
|
|
||||||
async def execute(self, action_input, context=None):
|
|
||||||
try:
|
|
||||||
import requests
|
|
||||||
# 使用requests进行网络请求
|
|
||||||
response = requests.get("https://api.example.com")
|
|
||||||
return {"status": "success", "data": response.json()}
|
|
||||||
except ImportError:
|
|
||||||
return {
|
|
||||||
"status": "error",
|
|
||||||
"message": "功能不可用:缺少requests库",
|
|
||||||
"hint": "请运行: pip install requests>=2.25.0"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤3:检查和管理
|
|
||||||
|
|
||||||
使用依赖管理API:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system import plugin_manager
|
|
||||||
|
|
||||||
# 检查所有插件的依赖状态
|
|
||||||
result = plugin_manager.check_all_dependencies()
|
|
||||||
print(f"检查了 {result['total_plugins_checked']} 个插件")
|
|
||||||
print(f"缺少必需依赖的插件: {result['plugins_with_missing_required']} 个")
|
|
||||||
|
|
||||||
# 生成requirements文件
|
|
||||||
plugin_manager.generate_plugin_requirements("plugin_requirements.txt")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 详细教程
|
## 📚 详细教程
|
||||||
|
|
||||||
@@ -97,11 +10,11 @@ plugin_manager.generate_plugin_requirements("plugin_requirements.txt")
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
PythonDependency(
|
PythonDependency(
|
||||||
package_name="requests", # 导入时的包名
|
package_name="PIL", # 导入时的包名
|
||||||
version=">=2.25.0", # 版本要求
|
version=">=11.2.0", # 版本要求
|
||||||
optional=False, # 是否为可选依赖
|
optional=False, # 是否为可选依赖
|
||||||
description="HTTP请求库", # 依赖描述
|
description="图像处理库", # 依赖描述
|
||||||
install_name="" # pip安装时的包名(可选)
|
install_name="pillow" # pip安装时的包名(可选)
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -110,10 +23,10 @@ PythonDependency(
|
|||||||
| 参数 | 类型 | 必需 | 说明 |
|
| 参数 | 类型 | 必需 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| `package_name` | str | ✅ | Python导入时使用的包名(如`requests`) |
|
| `package_name` | str | ✅ | Python导入时使用的包名(如`requests`) |
|
||||||
| `version` | str | ❌ | 版本要求,支持pip格式(如`>=1.0.0`, `==2.1.3`) |
|
| `version` | str | ❌ | 版本要求,使用pip格式(如`>=1.0.0`, `==2.1.3`) |
|
||||||
| `optional` | bool | ❌ | 是否为可选依赖,默认`False` |
|
| `optional` | bool | ❌ | 是否为可选依赖,默认`False` |
|
||||||
| `description` | str | ❌ | 依赖的用途描述 |
|
| `description` | str | ❌ | 依赖的用途描述 |
|
||||||
| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同 |
|
| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同,用于处理安装名称和导入名称不一致的情况 |
|
||||||
|
|
||||||
#### 版本格式示例
|
#### 版本格式示例
|
||||||
|
|
||||||
@@ -125,201 +38,3 @@ PythonDependency("pillow", "==8.3.2") # 精确版本
|
|||||||
PythonDependency("scipy", ">=1.7.0,!=1.8.0") # 排除特定版本
|
PythonDependency("scipy", ">=1.7.0,!=1.8.0") # 排除特定版本
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 特殊情况处理
|
|
||||||
|
|
||||||
**导入名与安装名不同的包:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
PythonDependency(
|
|
||||||
package_name="PIL", # import PIL
|
|
||||||
install_name="Pillow", # pip install Pillow
|
|
||||||
version=">=8.0.0"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**可选依赖示例:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
python_dependencies = [
|
|
||||||
# 必需依赖 - 核心功能
|
|
||||||
PythonDependency(
|
|
||||||
package_name="requests",
|
|
||||||
version=">=2.25.0",
|
|
||||||
description="HTTP库,插件核心功能必需"
|
|
||||||
),
|
|
||||||
|
|
||||||
# 可选依赖 - 增强功能
|
|
||||||
PythonDependency(
|
|
||||||
package_name="numpy",
|
|
||||||
version=">=1.20.0",
|
|
||||||
optional=True,
|
|
||||||
description="数值计算库,用于高级数学运算"
|
|
||||||
),
|
|
||||||
PythonDependency(
|
|
||||||
package_name="matplotlib",
|
|
||||||
version=">=3.0.0",
|
|
||||||
optional=True,
|
|
||||||
description="绘图库,用于数据可视化功能"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 依赖检查机制
|
|
||||||
|
|
||||||
系统在以下时机会自动检查依赖:
|
|
||||||
|
|
||||||
1. **插件加载时**:检查插件声明的所有依赖
|
|
||||||
2. **手动调用时**:通过API主动检查
|
|
||||||
3. **运行时检查**:在组件执行时动态检查
|
|
||||||
|
|
||||||
#### 检查结果状态
|
|
||||||
|
|
||||||
| 状态 | 描述 | 处理建议 |
|
|
||||||
|------|------|----------|
|
|
||||||
| `no_dependencies` | 插件未声明任何依赖 | 无需处理 |
|
|
||||||
| `ok` | 所有依赖都已满足 | 正常使用 |
|
|
||||||
| `missing_optional` | 缺少可选依赖 | 部分功能不可用,考虑安装 |
|
|
||||||
| `missing_required` | 缺少必需依赖 | 插件功能受限,需要安装 |
|
|
||||||
|
|
||||||
## 🎯 最佳实践
|
|
||||||
|
|
||||||
### 1. 依赖声明原则
|
|
||||||
|
|
||||||
#### ✅ 推荐做法
|
|
||||||
|
|
||||||
```python
|
|
||||||
python_dependencies = [
|
|
||||||
# 明确的版本要求
|
|
||||||
PythonDependency(
|
|
||||||
package_name="requests",
|
|
||||||
version=">=2.25.0,<3.0.0", # 主版本兼容
|
|
||||||
description="HTTP请求库,用于API调用"
|
|
||||||
),
|
|
||||||
|
|
||||||
# 合理的可选依赖
|
|
||||||
PythonDependency(
|
|
||||||
package_name="numpy",
|
|
||||||
version=">=1.20.0",
|
|
||||||
optional=True,
|
|
||||||
description="数值计算库,用于数据处理功能"
|
|
||||||
),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ❌ 避免的做法
|
|
||||||
|
|
||||||
```python
|
|
||||||
python_dependencies = [
|
|
||||||
# 过于宽泛的版本要求
|
|
||||||
PythonDependency("requests"), # 没有版本限制
|
|
||||||
|
|
||||||
# 过于严格的版本要求
|
|
||||||
PythonDependency("numpy", "==1.21.0"), # 精确版本过于严格
|
|
||||||
|
|
||||||
# 缺少描述
|
|
||||||
PythonDependency("matplotlib", ">=3.0.0"), # 没有说明用途
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 错误处理模式
|
|
||||||
|
|
||||||
#### 优雅降级模式
|
|
||||||
|
|
||||||
```python
|
|
||||||
class SmartAction(BaseAction):
|
|
||||||
async def execute(self, action_input, context=None):
|
|
||||||
# 检查可选依赖
|
|
||||||
try:
|
|
||||||
import numpy as np
|
|
||||||
# 使用numpy的高级功能
|
|
||||||
return await self._advanced_processing(action_input, np)
|
|
||||||
except ImportError:
|
|
||||||
# 降级到基础功能
|
|
||||||
return await self._basic_processing(action_input)
|
|
||||||
|
|
||||||
async def _advanced_processing(self, input_data, np):
|
|
||||||
"""使用numpy的高级处理"""
|
|
||||||
result = np.array(input_data).mean()
|
|
||||||
return {"result": result, "method": "advanced"}
|
|
||||||
|
|
||||||
async def _basic_processing(self, input_data):
|
|
||||||
"""基础处理(不依赖外部库)"""
|
|
||||||
result = sum(input_data) / len(input_data)
|
|
||||||
return {"result": result, "method": "basic"}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 使用API
|
|
||||||
|
|
||||||
### 检查依赖状态
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.plugin_system import plugin_manager
|
|
||||||
|
|
||||||
# 检查所有插件依赖(仅检查,不安装)
|
|
||||||
result = plugin_manager.check_all_dependencies(auto_install=False)
|
|
||||||
|
|
||||||
# 检查并自动安装缺失的必需依赖
|
|
||||||
result = plugin_manager.check_all_dependencies(auto_install=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 生成requirements文件
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 生成包含所有插件依赖的requirements文件
|
|
||||||
plugin_manager.generate_plugin_requirements("plugin_requirements.txt")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 获取依赖状态报告
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 获取详细的依赖检查报告
|
|
||||||
result = plugin_manager.check_all_dependencies()
|
|
||||||
for plugin_name, status in result['plugin_status'].items():
|
|
||||||
print(f"插件 {plugin_name}: {status['status']}")
|
|
||||||
if status['missing']:
|
|
||||||
print(f" 缺失必需依赖: {status['missing']}")
|
|
||||||
if status['optional_missing']:
|
|
||||||
print(f" 缺失可选依赖: {status['optional_missing']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛡️ 安全考虑
|
|
||||||
|
|
||||||
### 1. 自动安装控制
|
|
||||||
- 🛡️ **默认手动**: 自动安装默认关闭,需要明确启用
|
|
||||||
- 🔍 **依赖审查**: 安装前会显示将要安装的包列表
|
|
||||||
- ⏱️ **超时控制**: 安装操作有超时限制(5分钟)
|
|
||||||
|
|
||||||
### 2. 权限管理
|
|
||||||
- 📁 **环境隔离**: 推荐在虚拟环境中使用
|
|
||||||
- 🔒 **版本锁定**: 支持精确的版本控制
|
|
||||||
- 📝 **安装日志**: 记录所有安装操作
|
|
||||||
|
|
||||||
## 📊 故障排除
|
|
||||||
|
|
||||||
### 常见问题
|
|
||||||
|
|
||||||
1. **依赖检查失败**
|
|
||||||
```python
|
|
||||||
# 手动检查包是否可导入
|
|
||||||
try:
|
|
||||||
import package_name
|
|
||||||
print("包可用")
|
|
||||||
except ImportError:
|
|
||||||
print("包不可用,需要安装")
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **版本冲突**
|
|
||||||
```python
|
|
||||||
# 检查已安装的包版本
|
|
||||||
import package_name
|
|
||||||
print(f"当前版本: {package_name.__version__}")
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **安装失败**
|
|
||||||
```python
|
|
||||||
# 查看安装日志
|
|
||||||
from src.plugin_system import dependency_manager
|
|
||||||
result = dependency_manager.get_install_summary()
|
|
||||||
print("安装日志:", result['install_log'])
|
|
||||||
print("失败详情:", result['failed_installs'])
|
|
||||||
```
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -4,15 +4,34 @@
|
|||||||
|
|
||||||
## 新手入门
|
## 新手入门
|
||||||
|
|
||||||
- [📖 快速开始指南](quick-start.md) - 5分钟创建你的第一个插件
|
- [📖 快速开始指南](quick-start.md) - 快速创建你的第一个插件
|
||||||
|
|
||||||
## 组件功能详解
|
## 组件功能详解
|
||||||
|
|
||||||
- [🧱 Action组件详解](action-components.md) - 掌握最核心的Action组件
|
- [🧱 Action组件详解](action-components.md) - 掌握最核心的Action组件
|
||||||
- [💻 Command组件详解](command-components.md) - 学习直接响应命令的组件
|
- [💻 Command组件详解](command-components.md) - 学习直接响应命令的组件
|
||||||
- [⚙️ 配置管理指南](configuration-guide.md) - 学会使用自动生成的插件配置文件
|
- [⚙️ 配置文件系统指南](configuration-guide.md) - 学会使用自动生成的插件配置文件
|
||||||
- [📄 Manifest系统指南](manifest-guide.md) - 了解插件元数据管理和配置架构
|
- [📄 Manifest系统指南](manifest-guide.md) - 了解插件元数据管理和配置架构
|
||||||
|
|
||||||
|
Command vs Action 选择指南
|
||||||
|
|
||||||
|
1. 使用Command的场景
|
||||||
|
|
||||||
|
- ✅ 用户需要明确调用特定功能
|
||||||
|
- ✅ 需要精确的参数控制
|
||||||
|
- ✅ 管理和配置操作
|
||||||
|
- ✅ 查询和信息显示
|
||||||
|
- ✅ 系统维护命令
|
||||||
|
|
||||||
|
2. 使用Action的场景
|
||||||
|
|
||||||
|
- ✅ 增强麦麦的智能行为
|
||||||
|
- ✅ 根据上下文自动触发
|
||||||
|
- ✅ 情绪和表情表达
|
||||||
|
- ✅ 智能建议和帮助
|
||||||
|
- ✅ 随机化的互动
|
||||||
|
|
||||||
|
|
||||||
## API浏览
|
## API浏览
|
||||||
|
|
||||||
### 消息发送与处理API
|
### 消息发送与处理API
|
||||||
@@ -24,19 +43,22 @@
|
|||||||
- [LLM API](api/llm-api.md) - 大语言模型交互接口,可以使用内置LLM生成内容
|
- [LLM API](api/llm-api.md) - 大语言模型交互接口,可以使用内置LLM生成内容
|
||||||
- [✨ 回复生成器API](api/generator-api.md) - 智能回复生成接口,可以使用内置风格化生成器
|
- [✨ 回复生成器API](api/generator-api.md) - 智能回复生成接口,可以使用内置风格化生成器
|
||||||
|
|
||||||
### 表情包api
|
### 表情包API
|
||||||
- [😊 表情包API](api/emoji-api.md) - 表情包选择和管理接口
|
- [😊 表情包API](api/emoji-api.md) - 表情包选择和管理接口
|
||||||
|
|
||||||
### 关系系统api
|
### 关系系统API
|
||||||
- [人物信息API](api/person-api.md) - 用户信息,处理麦麦认识的人和关系的接口
|
- [人物信息API](api/person-api.md) - 用户信息,处理麦麦认识的人和关系的接口
|
||||||
|
|
||||||
### 数据与配置API
|
### 数据与配置API
|
||||||
- [🗄️ 数据库API](api/database-api.md) - 数据库操作接口
|
- [🗄️ 数据库API](api/database-api.md) - 数据库操作接口
|
||||||
- [⚙️ 配置API](api/config-api.md) - 配置读取和用户信息接口
|
- [⚙️ 配置API](api/config-api.md) - 配置读取和用户信息接口
|
||||||
|
|
||||||
### 工具API
|
### 插件和组件管理API
|
||||||
- [工具API](api/utils-api.md) - 文件操作、时间处理等工具函数
|
- [🔌 插件API](api/plugin-manage-api.md) - 插件加载和管理接口
|
||||||
|
- [🧩 组件API](api/component-manage-api.md) - 组件注册和管理接口
|
||||||
|
|
||||||
|
### 日志API
|
||||||
|
- [📜 日志API](api/logging-api.md) - logger实例获取接口
|
||||||
|
|
||||||
## 实验性
|
## 实验性
|
||||||
|
|
||||||
@@ -53,3 +75,9 @@
|
|||||||
2. 查看相关示例代码
|
2. 查看相关示例代码
|
||||||
3. 参考其他类似插件
|
3. 参考其他类似插件
|
||||||
4. 提交文档仓库issue
|
4. 提交文档仓库issue
|
||||||
|
|
||||||
|
## 一个方便的小设计
|
||||||
|
|
||||||
|
我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。
|
||||||
|
这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。
|
||||||
|
或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。
|
||||||
@@ -147,7 +147,7 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin
|
|||||||
## 📋 字段说明
|
## 📋 字段说明
|
||||||
|
|
||||||
### 基本信息
|
### 基本信息
|
||||||
- `manifest_version`: manifest格式版本,当前为3
|
- `manifest_version`: manifest格式版本,当前为1
|
||||||
- `name`: 插件显示名称(必需)
|
- `name`: 插件显示名称(必需)
|
||||||
- `version`: 插件版本号(必需)
|
- `version`: 插件版本号(必需)
|
||||||
- `description`: 插件功能描述(必需)
|
- `description`: 插件功能描述(必需)
|
||||||
@@ -165,10 +165,12 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin
|
|||||||
- `categories`: 分类数组(可选,建议填写)
|
- `categories`: 分类数组(可选,建议填写)
|
||||||
|
|
||||||
### 兼容性
|
### 兼容性
|
||||||
- `host_application`: 主机应用兼容性(可选)
|
- `host_application`: 主机应用兼容性(可选,建议填写)
|
||||||
- `min_version`: 最低兼容版本
|
- `min_version`: 最低兼容版本
|
||||||
- `max_version`: 最高兼容版本
|
- `max_version`: 最高兼容版本
|
||||||
|
|
||||||
|
⚠️ 在不填写的情况下,插件将默认支持所有版本。**(由于我们在不同版本对插件系统进行了大量的重构,这种情况几乎不可能。)**
|
||||||
|
|
||||||
### 国际化
|
### 国际化
|
||||||
- `default_locale`: 默认语言(可选)
|
- `default_locale`: 默认语言(可选)
|
||||||
- `locales_path`: 语言文件目录(可选)
|
- `locales_path`: 语言文件目录(可选)
|
||||||
@@ -185,24 +187,13 @@ python scripts/manifest_tool.py validate src/plugins/my_plugin
|
|||||||
2. **编码格式**:manifest文件必须使用UTF-8编码
|
2. **编码格式**:manifest文件必须使用UTF-8编码
|
||||||
3. **JSON格式**:文件必须是有效的JSON格式
|
3. **JSON格式**:文件必须是有效的JSON格式
|
||||||
4. **必需字段**:`manifest_version`、`name`、`version`、`description`、`author.name`是必需的
|
4. **必需字段**:`manifest_version`、`name`、`version`、`description`、`author.name`是必需的
|
||||||
5. **版本兼容**:当前只支持manifest_version = 3
|
5. **版本兼容**:当前只支持`manifest_version = 1`
|
||||||
|
|
||||||
## 🔍 常见问题
|
## 🔍 常见问题
|
||||||
|
|
||||||
### Q: 为什么要强制要求manifest文件?
|
|
||||||
A: Manifest文件提供了插件的标准化元数据,使得插件管理、依赖检查、版本兼容性验证等功能成为可能。
|
|
||||||
|
|
||||||
### Q: 可以不填写可选字段吗?
|
### Q: 可以不填写可选字段吗?
|
||||||
A: 可以。所有标记为"可选"的字段都可以不填写,但建议至少填写`license`和`keywords`。
|
A: 可以。所有标记为"可选"的字段都可以不填写,但建议至少填写`license`和`keywords`。
|
||||||
|
|
||||||
### Q: 如何快速为所有插件创建manifest?
|
|
||||||
A: 可以编写脚本批量处理:
|
|
||||||
```bash
|
|
||||||
# 扫描并为每个缺少manifest的插件创建最小化manifest
|
|
||||||
python scripts/manifest_tool.py scan src/plugins
|
|
||||||
# 然后手动为每个插件运行create-minimal命令
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: manifest验证失败怎么办?
|
### Q: manifest验证失败怎么办?
|
||||||
A: 根据验证器的错误提示修复相应问题。错误会导致插件加载失败,警告不会。
|
A: 根据验证器的错误提示修复相应问题。错误会导致插件加载失败,警告不会。
|
||||||
|
|
||||||
@@ -210,5 +201,5 @@ A: 根据验证器的错误提示修复相应问题。错误会导致插件加
|
|||||||
|
|
||||||
查看内置插件的manifest文件作为参考:
|
查看内置插件的manifest文件作为参考:
|
||||||
- `src/plugins/built_in/core_actions/_manifest.json`
|
- `src/plugins/built_in/core_actions/_manifest.json`
|
||||||
- `src/plugins/built_in/doubao_pic_plugin/_manifest.json`
|
|
||||||
- `src/plugins/built_in/tts_plugin/_manifest.json`
|
- `src/plugins/built_in/tts_plugin/_manifest.json`
|
||||||
|
- `src/plugins/hello_world_plugin/_manifest.json`
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
# 🚀 快速开始指南
|
# 🚀 快速开始指南
|
||||||
|
|
||||||
本指南将带你用5分钟时间,从零开始创建一个功能完整的MaiCore插件。
|
本指南将带你从零开始创建一个功能完整的MaiCore插件。
|
||||||
|
|
||||||
## 📖 概述
|
## 📖 概述
|
||||||
|
|
||||||
这个指南将带你快速创建你的第一个MaiCore插件。我们将创建一个简单的问候插件,展示插件系统的基本概念。无需阅读其他文档,跟着本指南就能完成!
|
这个指南将带你快速创建你的第一个MaiCore插件。我们将创建一个简单的问候插件,展示插件系统的基本概念。
|
||||||
|
|
||||||
## 🎯 学习目标
|
以下代码都在我们的`plugins/hello_world_plugin/`目录下。
|
||||||
|
|
||||||
- 理解插件的基本结构
|
### 一个方便的小设计
|
||||||
- 从最简单的插件开始,循序渐进
|
|
||||||
- 学会创建Action组件(智能动作)
|
|
||||||
- 学会创建Command组件(命令响应)
|
|
||||||
- 掌握配置Schema定义和配置文件自动生成(可选)
|
|
||||||
|
|
||||||
## 📂 准备工作
|
在开发中,我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。
|
||||||
|
这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。
|
||||||
|
或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。
|
||||||
|
|
||||||
|
### 📂 准备工作
|
||||||
|
|
||||||
确保你已经:
|
确保你已经:
|
||||||
|
|
||||||
@@ -26,16 +26,29 @@
|
|||||||
|
|
||||||
### 1. 创建插件目录
|
### 1. 创建插件目录
|
||||||
|
|
||||||
在项目根目录的 `plugins/` 文件夹下创建你的插件目录,目录名与插件名保持一致:
|
在项目根目录的 `plugins/` 文件夹下创建你的插件目录
|
||||||
|
|
||||||
可以用以下命令快速创建:
|
这里我们创建一个名为 `hello_world_plugin` 的目录
|
||||||
|
|
||||||
```bash
|
### 2. 创建`_manifest.json`文件
|
||||||
mkdir plugins/hello_world_plugin
|
|
||||||
cd plugins/hello_world_plugin
|
在插件目录下面创建一个 `_manifest.json` 文件,内容如下:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"manifest_version": 1,
|
||||||
|
"name": "Hello World 插件",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "一个简单的 Hello World 插件",
|
||||||
|
"author": {
|
||||||
|
"name": "你的名字"
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 创建最简单的插件
|
有关 `_manifest.json` 的详细说明,请参考 [Manifest文件指南](./manifest-guide.md)。
|
||||||
|
|
||||||
|
### 3. 创建最简单的插件
|
||||||
|
|
||||||
让我们从最基础的开始!创建 `plugin.py` 文件:
|
让我们从最基础的开始!创建 `plugin.py` 文件:
|
||||||
|
|
||||||
@@ -43,34 +56,33 @@ cd plugins/hello_world_plugin
|
|||||||
from typing import List, Tuple, Type
|
from typing import List, Tuple, Type
|
||||||
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
|
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
|
||||||
|
|
||||||
# ===== 插件注册 =====
|
@register_plugin # 注册插件
|
||||||
|
|
||||||
@register_plugin
|
|
||||||
class HelloWorldPlugin(BasePlugin):
|
class HelloWorldPlugin(BasePlugin):
|
||||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||||
|
|
||||||
# 插件基本信息(必须填写)
|
# 以下是插件基本信息和方法(必须填写)
|
||||||
plugin_name = "hello_world_plugin"
|
plugin_name = "hello_world_plugin"
|
||||||
plugin_description = "我的第一个MaiCore插件"
|
|
||||||
plugin_version = "1.0.0"
|
|
||||||
plugin_author = "你的名字"
|
|
||||||
enable_plugin = True # 启用插件
|
enable_plugin = True # 启用插件
|
||||||
|
dependencies = [] # 插件依赖列表(目前为空)
|
||||||
|
python_dependencies = [] # Python依赖列表(目前为空)
|
||||||
|
config_file_name = "config.toml" # 配置文件名
|
||||||
|
config_schema = {} # 配置文件模式(目前为空)
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: # 获取插件组件
|
||||||
"""返回插件包含的组件列表(目前是空的)"""
|
"""返回插件包含的组件列表(目前是空的)"""
|
||||||
return []
|
return []
|
||||||
```
|
```
|
||||||
|
|
||||||
🎉 **恭喜!你刚刚创建了一个最简单但完整的MaiCore插件!**
|
🎉 恭喜!你刚刚创建了一个最简单但完整的MaiCore插件!
|
||||||
|
|
||||||
**解释一下这些代码:**
|
**解释一下这些代码:**
|
||||||
|
|
||||||
- 首先,我们在plugin.py中定义了一个HelloWorldPulgin插件类,继承自 `BasePlugin` ,提供基本功能。
|
- 首先,我们在`plugin.py`中定义了一个HelloWorldPlugin插件类,继承自 `BasePlugin` ,提供基本功能。
|
||||||
- 通过给类加上,`@register_plugin` 装饰器,我们告诉系统"这是一个插件"
|
- 通过给类加上,`@register_plugin` 装饰器,我们告诉系统"这是一个插件"
|
||||||
- `plugin_name` 等是插件的基本信息,必须填写,**此部分必须与目录名称相同,否则插件无法使用**
|
- `plugin_name` 等是插件的基本信息,必须填写
|
||||||
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何action(动作)或者command(指令),是空的
|
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何 Action, Command 或者 EventHandler,所以返回空列表。
|
||||||
|
|
||||||
### 3. 测试基础插件
|
### 4. 测试基础插件
|
||||||
|
|
||||||
现在就可以测试这个插件了!启动MaiCore:
|
现在就可以测试这个插件了!启动MaiCore:
|
||||||
|
|
||||||
@@ -80,7 +92,7 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 4. 添加第一个功能:问候Action
|
### 5. 添加第一个功能:问候Action
|
||||||
|
|
||||||
现在我们要给插件加入一个有用的功能,我们从最好玩的Action做起
|
现在我们要给插件加入一个有用的功能,我们从最好玩的Action做起
|
||||||
|
|
||||||
@@ -107,40 +119,34 @@ class HelloAction(BaseAction):
|
|||||||
# === 基本信息(必须填写)===
|
# === 基本信息(必须填写)===
|
||||||
action_name = "hello_greeting"
|
action_name = "hello_greeting"
|
||||||
action_description = "向用户发送问候消息"
|
action_description = "向用户发送问候消息"
|
||||||
|
activation_type = ActionActivationType.ALWAYS # 始终激活
|
||||||
|
|
||||||
# === 功能描述(必须填写)===
|
# === 功能描述(必须填写)===
|
||||||
action_parameters = {
|
action_parameters = {"greeting_message": "要发送的问候消息"}
|
||||||
"greeting_message": "要发送的问候消息"
|
action_require = ["需要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
|
||||||
}
|
|
||||||
action_require = [
|
|
||||||
"需要发送友好问候时使用",
|
|
||||||
"当有人向你问好时使用",
|
|
||||||
"当你遇见没有见过的人时使用"
|
|
||||||
]
|
|
||||||
associated_types = ["text"]
|
associated_types = ["text"]
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
async def execute(self) -> Tuple[bool, str]:
|
||||||
"""执行问候动作 - 这是核心功能"""
|
"""执行问候动作 - 这是核心功能"""
|
||||||
# 发送问候消息
|
# 发送问候消息
|
||||||
greeting_message = self.action_data.get("greeting_message","")
|
greeting_message = self.action_data.get("greeting_message", "")
|
||||||
|
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
|
||||||
message = "嗨!很开心见到你!😊" + greeting_message
|
message = base_message + greeting_message
|
||||||
await self.send_text(message)
|
await self.send_text(message)
|
||||||
|
|
||||||
return True, "发送了问候消息"
|
return True, "发送了问候消息"
|
||||||
|
|
||||||
# ===== 插件注册 =====
|
|
||||||
|
|
||||||
@register_plugin
|
@register_plugin
|
||||||
class HelloWorldPlugin(BasePlugin):
|
class HelloWorldPlugin(BasePlugin):
|
||||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||||
|
|
||||||
# 插件基本信息
|
# 插件基本信息
|
||||||
plugin_name = "hello_world_plugin"
|
plugin_name = "hello_world_plugin"
|
||||||
plugin_description = "我的第一个MaiCore插件,包含问候功能"
|
|
||||||
plugin_version = "1.0.0"
|
|
||||||
plugin_author = "你的名字"
|
|
||||||
enable_plugin = True
|
enable_plugin = True
|
||||||
|
dependencies = []
|
||||||
|
python_dependencies = []
|
||||||
|
config_file_name = "config.toml"
|
||||||
|
config_schema = {}
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||||
"""返回插件包含的组件列表"""
|
"""返回插件包含的组件列表"""
|
||||||
@@ -150,13 +156,17 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
**新增内容解释:**
|
**解释一下这些代码:**
|
||||||
|
|
||||||
- `HelloAction` 是一个Action组件,MaiCore可能会选择使用它
|
- `HelloAction` 是我们定义的问候动作类,继承自 `BaseAction`,并实现了核心功能。
|
||||||
|
- 在 `HelloWorldPlugin` 中,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `HelloAction` 注册为插件的一个组件。
|
||||||
|
- 这样一来,当插件被加载时,问候动作也会被一并加载,并可以在MaiCore中使用。
|
||||||
- `execute()` 函数是Action的核心,定义了当Action被MaiCore选择后,具体要做什么
|
- `execute()` 函数是Action的核心,定义了当Action被MaiCore选择后,具体要做什么
|
||||||
- `self.send_text()` 是发送文本消息的便捷方法
|
- `self.send_text()` 是发送文本消息的便捷方法
|
||||||
|
|
||||||
### 5. 测试问候功能
|
Action 组件中有关`activation_type`、`action_parameters`、`action_require`、`associated_types` 等的详细说明请参考 [Action组件指南](./action-components.md)。
|
||||||
|
|
||||||
|
### 6. 测试问候Action
|
||||||
|
|
||||||
重启MaiCore,然后在聊天中发送任意消息,比如:
|
重启MaiCore,然后在聊天中发送任意消息,比如:
|
||||||
|
|
||||||
@@ -174,96 +184,17 @@ MaiCore可能会选择使用你的问候Action,发送回复:
|
|||||||
|
|
||||||
> **💡 小提示**:MaiCore会智能地决定什么时候使用它。如果没有立即看到效果,多试几次不同的消息。
|
> **💡 小提示**:MaiCore会智能地决定什么时候使用它。如果没有立即看到效果,多试几次不同的消息。
|
||||||
|
|
||||||
🎉 **太棒了!你的插件已经有实际功能了!**
|
🎉 太棒了!你的插件已经有实际功能了!
|
||||||
|
|
||||||
### 5.5. 了解激活系统(重要概念)
|
### 7. 添加第二个功能:时间查询Command
|
||||||
|
|
||||||
Action固然好用简单,但是现在有个问题,当用户加载了非常多的插件,添加了很多自定义Action,LLM需要选择的Action也会变多
|
|
||||||
|
|
||||||
而不断增多的Action会加大LLM的消耗和负担,降低Action使用的精准度。而且我们并不需要LLM在所有时候都考虑所有Action
|
|
||||||
|
|
||||||
例如,当群友只是在进行正常的聊天,就没有必要每次都考虑是否要选择“禁言”动作,这不仅影响决策速度,还会增加消耗。
|
|
||||||
|
|
||||||
那有什么办法,能够让Action有选择的加入MaiCore的决策池呢?
|
|
||||||
|
|
||||||
**什么是激活系统?**
|
|
||||||
激活系统决定了什么时候你的Action会被MaiCore"考虑"使用:
|
|
||||||
|
|
||||||
- **`ActionActivationType.ALWAYS`** - 总是可用(默认值)
|
|
||||||
- **`ActionActivationType.KEYWORD`** - 只有消息包含特定关键词时才可用
|
|
||||||
- **`ActionActivationType.PROBABILITY`** - 根据概率随机可用
|
|
||||||
- **`ActionActivationType.NEVER`** - 永不可用(用于调试)
|
|
||||||
|
|
||||||
> **💡 使用提示**:
|
|
||||||
>
|
|
||||||
> - 推荐使用枚举类型(如 `ActionActivationType.ALWAYS`),有代码提示和类型检查
|
|
||||||
> - 也可以直接使用字符串(如 `"always"`),系统都支持
|
|
||||||
|
|
||||||
### 5.6. 进阶:尝试关键词激活(可选)
|
|
||||||
|
|
||||||
现在让我们尝试一个更精确的激活方式!添加一个只在用户说特定关键词时才激活的Action:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 在HelloAction后面添加这个新Action
|
|
||||||
class ByeAction(BaseAction):
|
|
||||||
"""告别Action - 只在用户说再见时激活"""
|
|
||||||
|
|
||||||
action_name = "bye_greeting"
|
|
||||||
action_description = "向用户发送告别消息"
|
|
||||||
|
|
||||||
# 使用关键词激活
|
|
||||||
focus_activation_type = ActionActivationType.KEYWORD
|
|
||||||
normal_activation_type = ActionActivationType.KEYWORD
|
|
||||||
|
|
||||||
# 关键词设置
|
|
||||||
activation_keywords = ["再见", "bye", "88", "拜拜"]
|
|
||||||
keyword_case_sensitive = False
|
|
||||||
|
|
||||||
action_parameters = {"bye_message": "要发送的告别消息"}
|
|
||||||
action_require = [
|
|
||||||
"用户要告别时使用",
|
|
||||||
"当有人要离开时使用",
|
|
||||||
"当有人和你说再见时使用",
|
|
||||||
]
|
|
||||||
associated_types = ["text"]
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
|
||||||
bye_message = self.action_data.get("bye_message","")
|
|
||||||
|
|
||||||
message = "再见!期待下次聊天!👋" + bye_message
|
|
||||||
await self.send_text(message)
|
|
||||||
return True, "发送了告别消息"
|
|
||||||
```
|
|
||||||
|
|
||||||
然后在插件注册中添加这个Action:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
|
||||||
return [
|
|
||||||
(HelloAction.get_action_info(), HelloAction),
|
|
||||||
(ByeAction.get_action_info(), ByeAction), # 添加告别Action
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
现在测试:发送"再见",应该会触发告别Action!
|
|
||||||
|
|
||||||
**关键词激活的特点:**
|
|
||||||
|
|
||||||
- 更精确:只在包含特定关键词时才会被考虑
|
|
||||||
- 更可预测:用户知道说什么会触发什么功能
|
|
||||||
- 更适合:特定场景或命令式的功能
|
|
||||||
|
|
||||||
### 6. 添加第二个功能:时间查询Command
|
|
||||||
|
|
||||||
现在让我们添加一个Command组件。Command和Action不同,它是直接响应用户命令的:
|
现在让我们添加一个Command组件。Command和Action不同,它是直接响应用户命令的:
|
||||||
|
|
||||||
Command是最简单,最直接的相应,不由LLM判断选择使用
|
Command是最简单,最直接的响应,不由LLM判断选择使用
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 在现有代码基础上,添加Command组件
|
# 在现有代码基础上,添加Command组件
|
||||||
|
import datetime
|
||||||
# ===== Command组件 =====
|
|
||||||
|
|
||||||
from src.plugin_system import BaseCommand
|
from src.plugin_system import BaseCommand
|
||||||
#导入Command基类
|
#导入Command基类
|
||||||
|
|
||||||
@@ -275,53 +206,49 @@ class TimeCommand(BaseCommand):
|
|||||||
|
|
||||||
# === 命令设置(必须填写)===
|
# === 命令设置(必须填写)===
|
||||||
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
|
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
|
||||||
command_help = "查询当前时间"
|
|
||||||
command_examples = ["/time"]
|
|
||||||
intercept_message = True # 拦截消息,不让其他组件处理
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
async def execute(self) -> Tuple[bool, Optional[str], bool]:
|
||||||
"""执行时间查询"""
|
"""执行时间查询"""
|
||||||
import datetime
|
|
||||||
|
|
||||||
# 获取当前时间
|
# 获取当前时间
|
||||||
time_format = self.get_config("time.format", "%Y-%m-%d %H:%M:%S")
|
time_format: str = "%Y-%m-%d %H:%M:%S"
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
time_str = now.strftime(time_format)
|
time_str = now.strftime(time_format)
|
||||||
|
|
||||||
# 发送时间信息
|
# 发送时间信息
|
||||||
message = f"⏰ 当前时间:{time_str}"
|
message = f"⏰ 当前时间:{time_str}"
|
||||||
await self.send_text(message)
|
await self.send_text(message)
|
||||||
|
|
||||||
return True, f"显示了当前时间: {time_str}"
|
|
||||||
|
|
||||||
# ===== 插件注册 =====
|
return True, f"显示了当前时间: {time_str}", True
|
||||||
|
|
||||||
@register_plugin
|
@register_plugin
|
||||||
class HelloWorldPlugin(BasePlugin):
|
class HelloWorldPlugin(BasePlugin):
|
||||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||||
|
|
||||||
|
# 插件基本信息
|
||||||
plugin_name = "hello_world_plugin"
|
plugin_name = "hello_world_plugin"
|
||||||
plugin_description = "我的第一个MaiCore插件,包含问候和时间查询功能"
|
|
||||||
plugin_version = "1.0.0"
|
|
||||||
plugin_author = "你的名字"
|
|
||||||
enable_plugin = True
|
enable_plugin = True
|
||||||
|
dependencies = []
|
||||||
|
python_dependencies = []
|
||||||
|
config_file_name = "config.toml"
|
||||||
|
config_schema = {}
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||||
return [
|
return [
|
||||||
(HelloAction.get_action_info(), HelloAction),
|
(HelloAction.get_action_info(), HelloAction),
|
||||||
(ByeAction.get_action_info(), ByeAction),
|
|
||||||
(TimeCommand.get_command_info(), TimeCommand),
|
(TimeCommand.get_command_info(), TimeCommand),
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
同样的,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `TimeCommand` 注册为插件的一个组件。
|
||||||
|
|
||||||
**Command组件解释:**
|
**Command组件解释:**
|
||||||
|
|
||||||
- Command是直接响应用户命令的组件
|
|
||||||
- `command_pattern` 使用正则表达式匹配用户输入
|
- `command_pattern` 使用正则表达式匹配用户输入
|
||||||
- `^/time$` 表示精确匹配 "/time"
|
- `^/time$` 表示精确匹配 "/time"
|
||||||
- `intercept_message = True` 表示处理完命令后不再让其他组件处理
|
|
||||||
|
|
||||||
### 7. 测试时间查询功能
|
有关 Command 组件的更多信息,请参考 [Command组件指南](./command-components.md)。
|
||||||
|
|
||||||
|
### 8. 测试时间查询Command
|
||||||
|
|
||||||
重启MaiCore,发送命令:
|
重启MaiCore,发送命令:
|
||||||
|
|
||||||
@@ -332,106 +259,147 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
你应该会收到回复:
|
你应该会收到回复:
|
||||||
|
|
||||||
```
|
```
|
||||||
⏰ 当前时间:2024-01-01 12:30:45
|
⏰ 当前时间:2024-01-01 12:00:00
|
||||||
```
|
```
|
||||||
|
|
||||||
🎉 **太棒了!现在你的插件有3个功能了!**
|
🎉 太棒了!现在你已经了解了基本的 Action 和 Command 组件的使用方法。你可以根据自己的需求,继续扩展插件的功能,添加更多的 Action 和 Command 组件,让你的插件更加丰富和强大!
|
||||||
|
|
||||||
### 8. 添加配置文件(可选进阶)
|
---
|
||||||
|
|
||||||
如果你想让插件更加灵活,可以添加配置支持。
|
## 进阶教程
|
||||||
|
|
||||||
|
如果你想让插件更加灵活和强大,可以参考接下来的进阶教程。
|
||||||
|
|
||||||
|
### 1. 添加配置文件
|
||||||
|
|
||||||
|
想要为插件添加配置文件吗?让我们一起来配置`config_schema`属性!
|
||||||
|
|
||||||
> **🚨 重要:不要手动创建config.toml文件!**
|
> **🚨 重要:不要手动创建config.toml文件!**
|
||||||
>
|
>
|
||||||
> 我们需要在插件代码中定义配置Schema,让系统自动生成配置文件。
|
> 我们需要在插件代码中定义配置Schema,让系统自动生成配置文件。
|
||||||
|
|
||||||
#### 📄 配置架构说明
|
|
||||||
|
|
||||||
在新的插件系统中,我们采用了**职责分离**的设计:
|
|
||||||
|
|
||||||
- **`_manifest.json`** - 插件元数据(名称、版本、描述、作者等)
|
|
||||||
- **`config.toml`** - 运行时配置(启用状态、功能参数等)
|
|
||||||
|
|
||||||
这样避免了信息重复,提高了维护性。
|
|
||||||
|
|
||||||
首先,在插件类中定义配置Schema:
|
首先,在插件类中定义配置Schema:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.plugin_system.base.config_types import ConfigField
|
from src.plugin_system import ConfigField
|
||||||
|
|
||||||
@register_plugin
|
@register_plugin
|
||||||
class HelloWorldPlugin(BasePlugin):
|
class HelloWorldPlugin(BasePlugin):
|
||||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||||
|
|
||||||
plugin_name = "hello_world_plugin"
|
# 插件基本信息
|
||||||
plugin_description = "我的第一个MaiCore插件,包含问候和时间查询功能"
|
plugin_name: str = "hello_world_plugin" # 内部标识符
|
||||||
plugin_version = "1.0.0"
|
enable_plugin: bool = True
|
||||||
plugin_author = "你的名字"
|
dependencies: List[str] = [] # 插件依赖列表
|
||||||
enable_plugin = True
|
python_dependencies: List[str] = [] # Python包依赖列表
|
||||||
config_file_name = "config.toml" # 配置文件名
|
config_file_name: str = "config.toml" # 配置文件名
|
||||||
|
|
||||||
# 配置节描述
|
|
||||||
config_section_descriptions = {
|
|
||||||
"plugin": "插件启用配置",
|
|
||||||
"greeting": "问候功能配置",
|
|
||||||
"time": "时间查询配置"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 配置Schema定义
|
# 配置Schema定义
|
||||||
config_schema = {
|
config_schema: dict = {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件")
|
"name": ConfigField(type=str, default="hello_world_plugin", description="插件名称"),
|
||||||
|
"version": ConfigField(type=str, default="1.0.0", description="插件版本"),
|
||||||
|
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||||
},
|
},
|
||||||
"greeting": {
|
"greeting": {
|
||||||
"message": ConfigField(
|
"message": ConfigField(type=str, default="嗨!很开心见到你!😊", description="默认问候消息"),
|
||||||
type=str,
|
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
|
||||||
default="嗨!很开心见到你!😊",
|
|
||||||
description="默认问候消息"
|
|
||||||
),
|
|
||||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号")
|
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {"format": ConfigField(type=str, default="%Y-%m-%d %H:%M:%S", description="时间显示格式")},
|
||||||
"format": ConfigField(
|
|
||||||
type=str,
|
|
||||||
default="%Y-%m-%d %H:%M:%S",
|
|
||||||
description="时间显示格式"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||||
return [
|
return [
|
||||||
(HelloAction.get_action_info(), HelloAction),
|
(HelloAction.get_action_info(), HelloAction),
|
||||||
(ByeAction.get_action_info(), ByeAction),
|
|
||||||
(TimeCommand.get_command_info(), TimeCommand),
|
(TimeCommand.get_command_info(), TimeCommand),
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
然后修改Action和Command代码,让它们读取配置:
|
这会生成一个如下的 `config.toml` 文件:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# hello_world_plugin - 自动生成的配置文件
|
||||||
|
# 我的第一个MaiCore插件,包含问候功能和时间查询等基础示例
|
||||||
|
|
||||||
|
# 插件基本信息
|
||||||
|
[plugin]
|
||||||
|
|
||||||
|
# 插件名称
|
||||||
|
name = "hello_world_plugin"
|
||||||
|
|
||||||
|
# 插件版本
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
# 是否启用插件
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
|
||||||
|
# 问候功能配置
|
||||||
|
[greeting]
|
||||||
|
|
||||||
|
# 默认问候消息
|
||||||
|
message = "嗨!很开心见到你!😊"
|
||||||
|
|
||||||
|
# 是否启用表情符号
|
||||||
|
enable_emoji = true
|
||||||
|
|
||||||
|
|
||||||
|
# 时间查询配置
|
||||||
|
[time]
|
||||||
|
|
||||||
|
# 时间显示格式
|
||||||
|
format = "%Y-%m-%d %H:%M:%S"
|
||||||
|
```
|
||||||
|
|
||||||
|
然后修改Action和Command代码,通过 `get_config()` 方法让它们读取配置(配置的键是命名空间式的):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# 在HelloAction的execute方法中:
|
class HelloAction(BaseAction):
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
"""问候Action - 简单的问候动作"""
|
||||||
# 从配置文件读取问候消息
|
|
||||||
greeting_message = self.action_data.get("greeting_message", "")
|
|
||||||
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
|
|
||||||
|
|
||||||
message = base_message + greeting_message
|
|
||||||
await self.send_text(message)
|
|
||||||
return True, "发送了问候消息"
|
|
||||||
|
|
||||||
# 在TimeCommand的execute方法中:
|
# === 基本信息(必须填写)===
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
action_name = "hello_greeting"
|
||||||
import datetime
|
action_description = "向用户发送问候消息"
|
||||||
|
activation_type = ActionActivationType.ALWAYS # 始终激活
|
||||||
# 从配置文件读取时间格式
|
|
||||||
time_format = self.get_config("time.format", "%Y-%m-%d %H:%M:%S")
|
# === 功能描述(必须填写)===
|
||||||
now = datetime.datetime.now()
|
action_parameters = {"greeting_message": "要发送的问候消息"}
|
||||||
time_str = now.strftime(time_format)
|
action_require = ["需要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
|
||||||
|
associated_types = ["text"]
|
||||||
message = f"⏰ 当前时间:{time_str}"
|
|
||||||
await self.send_text(message)
|
async def execute(self) -> Tuple[bool, str]:
|
||||||
return True, f"显示了当前时间: {time_str}"
|
"""执行问候动作 - 这是核心功能"""
|
||||||
|
# 发送问候消息
|
||||||
|
greeting_message = self.action_data.get("greeting_message", "")
|
||||||
|
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
|
||||||
|
message = base_message + greeting_message
|
||||||
|
await self.send_text(message)
|
||||||
|
|
||||||
|
return True, "发送了问候消息"
|
||||||
|
|
||||||
|
class TimeCommand(BaseCommand):
|
||||||
|
"""时间查询Command - 响应/time命令"""
|
||||||
|
|
||||||
|
command_name = "time"
|
||||||
|
command_description = "查询当前时间"
|
||||||
|
|
||||||
|
# === 命令设置(必须填写)===
|
||||||
|
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
|
||||||
|
|
||||||
|
async def execute(self) -> Tuple[bool, str, bool]:
|
||||||
|
"""执行时间查询"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# 获取当前时间
|
||||||
|
time_format: str = self.get_config("time.format", "%Y-%m-%d %H:%M:%S") # type: ignore
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
time_str = now.strftime(time_format)
|
||||||
|
|
||||||
|
# 发送时间信息
|
||||||
|
message = f"⏰ 当前时间:{time_str}"
|
||||||
|
await self.send_text(message)
|
||||||
|
|
||||||
|
return True, f"显示了当前时间: {time_str}", True
|
||||||
```
|
```
|
||||||
|
|
||||||
**配置系统工作流程:**
|
**配置系统工作流程:**
|
||||||
@@ -441,47 +409,20 @@ async def execute(self) -> Tuple[bool, str]:
|
|||||||
3. **用户修改**: 用户可以修改生成的配置文件
|
3. **用户修改**: 用户可以修改生成的配置文件
|
||||||
4. **代码读取**: 使用 `self.get_config()` 读取配置值
|
4. **代码读取**: 使用 `self.get_config()` 读取配置值
|
||||||
|
|
||||||
**配置功能解释:**
|
**绝对不要手动创建 `config.toml` 文件!**
|
||||||
|
|
||||||
- `self.get_config()` 可以读取配置文件中的值
|
更详细的配置系统介绍请参考 [配置指南](./configuration-guide.md)。
|
||||||
- 第一个参数是配置路径(用点分隔),第二个参数是默认值
|
|
||||||
- 配置文件会包含详细的注释和说明,用户可以轻松理解和修改
|
|
||||||
- **绝不要手动创建配置文件**,让系统自动生成
|
|
||||||
|
|
||||||
### 9. 创建说明文档(可选)
|
### 2. 创建说明文档
|
||||||
|
|
||||||
创建 `README.md` 文件来说明你的插件:
|
你可以创建一个 `README.md` 文件,描述插件的功能和使用方法。
|
||||||
|
|
||||||
```markdown
|
### 3. 发布到插件市场
|
||||||
# Hello World 插件
|
|
||||||
|
|
||||||
## 概述
|
如果你想让更多人使用你的插件,可以将它发布到MaiCore的插件市场。
|
||||||
我的第一个MaiCore插件,包含问候和时间查询功能。
|
|
||||||
|
|
||||||
## 功能
|
这部分请参考 [plugin-repo](https://github.com/Maim-with-u/plugin-repo) 的文档。
|
||||||
- **问候功能**: 当用户说"你好"、"hello"、"hi"时自动回复
|
|
||||||
- **时间查询**: 发送 `/time` 命令查询当前时间
|
|
||||||
|
|
||||||
## 使用方法
|
---
|
||||||
### 问候功能
|
|
||||||
发送包含以下关键词的消息:
|
|
||||||
- "你好"
|
|
||||||
- "hello"
|
|
||||||
- "hi"
|
|
||||||
|
|
||||||
### 时间查询
|
🎉 恭喜你!你已经成功的创建了自己的插件了!
|
||||||
发送命令:`/time`
|
|
||||||
|
|
||||||
## 配置文件
|
|
||||||
插件会自动生成 `config.toml` 配置文件,用户可以修改:
|
|
||||||
- 问候消息内容
|
|
||||||
- 时间显示格式
|
|
||||||
- 插件启用状态
|
|
||||||
|
|
||||||
注意:配置文件是自动生成的,不要手动创建!
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
# 🔧 工具系统详解
|
# 🔧 工具系统详解
|
||||||
|
|
||||||
## 📖 什么是工具系统
|
## 📖 什么是工具
|
||||||
|
|
||||||
工具系统是MaiBot的信息获取能力扩展组件,**专门用于在Focus模式下扩宽麦麦能够获得的信息量**。如果说Action组件功能五花八门,可以拓展麦麦能做的事情,那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。
|
工具是MaiBot的信息获取能力扩展组件。如果说Action组件功能五花八门,可以拓展麦麦能做的事情,那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。
|
||||||
|
|
||||||
### 🎯 工具系统的特点
|
### 🎯 工具的特点
|
||||||
|
|
||||||
- 🔍 **信息获取增强**:扩展麦麦获取外部信息的能力
|
- 🔍 **信息获取增强**:扩展麦麦获取外部信息的能力
|
||||||
- 🎯 **Focus模式专用**:仅在专注聊天模式下工作,必须开启工具处理器
|
|
||||||
- 📊 **数据丰富**:帮助麦麦获得更多背景信息和实时数据
|
- 📊 **数据丰富**:帮助麦麦获得更多背景信息和实时数据
|
||||||
- 🔌 **插件式架构**:支持独立开发和注册新工具
|
- 🔌 **插件式架构**:支持独立开发和注册新工具
|
||||||
- ⚡ **自动发现**:工具会被系统自动识别和注册
|
- ⚡ **自动发现**:工具会被系统自动识别和注册
|
||||||
@@ -17,19 +16,15 @@
|
|||||||
| 特征 | Action | Command | Tool |
|
| 特征 | Action | Command | Tool |
|
||||||
|-----|-------|---------|------|
|
|-----|-------|---------|------|
|
||||||
| **主要用途** | 扩展麦麦行为能力 | 响应用户指令 | 扩展麦麦信息获取 |
|
| **主要用途** | 扩展麦麦行为能力 | 响应用户指令 | 扩展麦麦信息获取 |
|
||||||
| **适用模式** | 所有模式 | 所有模式 | 仅Focus模式 |
|
|
||||||
| **触发方式** | 麦麦智能决策 | 用户主动触发 | LLM根据需要调用 |
|
| **触发方式** | 麦麦智能决策 | 用户主动触发 | LLM根据需要调用 |
|
||||||
| **目标** | 让麦麦做更多事情 | 提供具体功能 | 让麦麦知道更多信息 |
|
| **目标** | 让麦麦做更多事情 | 提供具体功能 | 让麦麦知道更多信息 |
|
||||||
| **使用场景** | 增强交互体验 | 功能服务 | 信息查询和分析 |
|
| **使用场景** | 增强交互体验 | 功能服务 | 信息查询和分析 |
|
||||||
|
|
||||||
## 🏗️ 工具基本结构
|
## 🏗️ Tool组件的基本结构
|
||||||
|
|
||||||
### 必要组件
|
|
||||||
|
|
||||||
每个工具必须继承 `BaseTool` 基类并实现以下属性和方法:
|
每个工具必须继承 `BaseTool` 基类并实现以下属性和方法:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.tools.tool_can_use.base_tool import BaseTool, register_tool
|
from src.plugin_system import BaseTool
|
||||||
|
|
||||||
class MyTool(BaseTool):
|
class MyTool(BaseTool):
|
||||||
# 工具名称,必须唯一
|
# 工具名称,必须唯一
|
||||||
@@ -53,8 +48,10 @@ class MyTool(BaseTool):
|
|||||||
},
|
},
|
||||||
"required": ["query"]
|
"required": ["query"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
available_for_llm = True # 是否对LLM可用
|
||||||
|
|
||||||
async def execute(self, function_args, message_txt=""):
|
async def execute(self, function_args: Dict[str, Any]):
|
||||||
"""执行工具逻辑"""
|
"""执行工具逻辑"""
|
||||||
# 实现工具功能
|
# 实现工具功能
|
||||||
result = f"查询结果: {function_args.get('query')}"
|
result = f"查询结果: {function_args.get('query')}"
|
||||||
@@ -63,9 +60,6 @@ class MyTool(BaseTool):
|
|||||||
"name": self.name,
|
"name": self.name,
|
||||||
"content": result
|
"content": result
|
||||||
}
|
}
|
||||||
|
|
||||||
# 注册工具
|
|
||||||
register_tool(MyTool)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 属性说明
|
### 属性说明
|
||||||
@@ -80,39 +74,16 @@ register_tool(MyTool)
|
|||||||
|
|
||||||
| 方法 | 参数 | 返回值 | 说明 |
|
| 方法 | 参数 | 返回值 | 说明 |
|
||||||
|-----|------|--------|------|
|
|-----|------|--------|------|
|
||||||
| `execute` | `function_args`, `message_txt` | `dict` | 执行工具核心逻辑 |
|
| `execute` | `function_args` | `dict` | 执行工具核心逻辑 |
|
||||||
|
|
||||||
## 🔄 自动注册机制
|
---
|
||||||
|
|
||||||
工具系统采用自动发现和注册机制:
|
|
||||||
|
|
||||||
1. **文件扫描**:系统自动遍历 `tool_can_use` 目录中的所有Python文件
|
|
||||||
2. **类识别**:寻找继承自 `BaseTool` 的工具类
|
|
||||||
3. **自动注册**:调用 `register_tool()` 的工具会被注册到系统中
|
|
||||||
4. **即用即加载**:工具在需要时被实例化和调用
|
|
||||||
|
|
||||||
### 注册流程
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 1. 创建工具类
|
|
||||||
class WeatherTool(BaseTool):
|
|
||||||
name = "weather_query"
|
|
||||||
description = "查询指定城市的天气信息"
|
|
||||||
# ...
|
|
||||||
|
|
||||||
# 2. 注册工具(在文件末尾)
|
|
||||||
register_tool(WeatherTool)
|
|
||||||
|
|
||||||
# 3. 系统自动发现(无需手动操作)
|
|
||||||
# discover_tools() 函数会自动完成注册
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎨 完整工具示例
|
## 🎨 完整工具示例
|
||||||
|
|
||||||
### 天气查询工具
|
完成一个天气查询工具
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from src.tools.tool_can_use.base_tool import BaseTool, register_tool
|
from src.plugin_system import BaseTool
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -192,217 +163,16 @@ class WeatherTool(BaseTool):
|
|||||||
💧 湿度: {humidity}%
|
💧 湿度: {humidity}%
|
||||||
━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
# 注册工具
|
|
||||||
register_tool(WeatherTool)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 知识查询工具
|
---
|
||||||
|
|
||||||
```python
|
|
||||||
from src.tools.tool_can_use.base_tool import BaseTool, register_tool
|
|
||||||
|
|
||||||
class KnowledgeSearchTool(BaseTool):
|
|
||||||
"""知识搜索工具 - 查询百科知识和专业信息"""
|
|
||||||
|
|
||||||
name = "knowledge_search"
|
|
||||||
description = "搜索百科知识、专业术语解释、历史事件等信息"
|
|
||||||
|
|
||||||
parameters = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"query": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "要搜索的知识关键词或问题"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "知识分类:science(科学)、history(历史)、technology(技术)、general(通用)等",
|
|
||||||
"enum": ["science", "history", "technology", "general"]
|
|
||||||
},
|
|
||||||
"language": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "结果语言:zh(中文)、en(英文)",
|
|
||||||
"enum": ["zh", "en"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["query"]
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, function_args, message_txt=""):
|
|
||||||
"""执行知识搜索"""
|
|
||||||
try:
|
|
||||||
query = function_args.get("query")
|
|
||||||
category = function_args.get("category", "general")
|
|
||||||
language = function_args.get("language", "zh")
|
|
||||||
|
|
||||||
# 执行搜索逻辑
|
|
||||||
search_results = await self._search_knowledge(query, category, language)
|
|
||||||
|
|
||||||
# 格式化结果
|
|
||||||
result = self._format_search_results(query, search_results)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"name": self.name,
|
|
||||||
"content": result
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return {
|
|
||||||
"name": self.name,
|
|
||||||
"content": f"知识搜索失败: {str(e)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
async def _search_knowledge(self, query: str, category: str, language: str) -> list:
|
|
||||||
"""执行知识搜索"""
|
|
||||||
# 这里实现实际的搜索逻辑
|
|
||||||
# 可以对接维基百科API、百度百科API等
|
|
||||||
|
|
||||||
# 示例返回数据
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"title": f"{query}的定义",
|
|
||||||
"summary": f"关于{query}的详细解释...",
|
|
||||||
"source": "Wikipedia"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
def _format_search_results(self, query: str, results: list) -> str:
|
|
||||||
"""格式化搜索结果"""
|
|
||||||
if not results:
|
|
||||||
return f"未找到关于 '{query}' 的相关信息"
|
|
||||||
|
|
||||||
formatted_text = f"📚 关于 '{query}' 的搜索结果:\n\n"
|
|
||||||
|
|
||||||
for i, result in enumerate(results[:3], 1): # 限制显示前3条
|
|
||||||
title = result.get("title", "无标题")
|
|
||||||
summary = result.get("summary", "无摘要")
|
|
||||||
source = result.get("source", "未知来源")
|
|
||||||
|
|
||||||
formatted_text += f"{i}. **{title}**\n"
|
|
||||||
formatted_text += f" {summary}\n"
|
|
||||||
formatted_text += f" 📖 来源: {source}\n\n"
|
|
||||||
|
|
||||||
return formatted_text.strip()
|
|
||||||
|
|
||||||
# 注册工具
|
|
||||||
register_tool(KnowledgeSearchTool)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 工具开发步骤
|
|
||||||
|
|
||||||
### 1. 创建工具文件
|
|
||||||
|
|
||||||
在 `src/tools/tool_can_use/` 目录下创建新的Python文件:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 例如创建 my_new_tool.py
|
|
||||||
touch src/tools/tool_can_use/my_new_tool.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 实现工具类
|
|
||||||
|
|
||||||
```python
|
|
||||||
from src.tools.tool_can_use.base_tool import BaseTool, register_tool
|
|
||||||
|
|
||||||
class MyNewTool(BaseTool):
|
|
||||||
name = "my_new_tool"
|
|
||||||
description = "新工具的功能描述"
|
|
||||||
|
|
||||||
parameters = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
# 定义参数
|
|
||||||
},
|
|
||||||
"required": []
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, function_args, message_txt=""):
|
|
||||||
# 实现工具逻辑
|
|
||||||
return {
|
|
||||||
"name": self.name,
|
|
||||||
"content": "执行结果"
|
|
||||||
}
|
|
||||||
|
|
||||||
register_tool(MyNewTool)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 测试工具
|
|
||||||
|
|
||||||
创建测试文件验证工具功能:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import asyncio
|
|
||||||
from my_new_tool import MyNewTool
|
|
||||||
|
|
||||||
async def test_tool():
|
|
||||||
tool = MyNewTool()
|
|
||||||
result = await tool.execute({"param": "value"})
|
|
||||||
print(result)
|
|
||||||
|
|
||||||
asyncio.run(test_tool())
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 系统集成
|
|
||||||
|
|
||||||
工具创建完成后,系统会自动发现和注册,无需额外配置。
|
|
||||||
|
|
||||||
## ⚙️ 工具处理器配置
|
|
||||||
|
|
||||||
### 启用工具处理器
|
|
||||||
|
|
||||||
工具系统仅在Focus模式下工作,需要确保工具处理器已启用:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 在Focus模式配置中
|
|
||||||
focus_config = {
|
|
||||||
"enable_tool_processor": True, # 必须启用
|
|
||||||
"tool_timeout": 30, # 工具执行超时时间(秒)
|
|
||||||
"max_tools_per_message": 3 # 单次消息最大工具调用数
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 工具使用流程
|
|
||||||
|
|
||||||
1. **用户发送消息**:在Focus模式下发送需要信息查询的消息
|
|
||||||
2. **LLM判断需求**:麦麦分析消息,判断是否需要使用工具获取信息
|
|
||||||
3. **选择工具**:根据需求选择合适的工具
|
|
||||||
4. **调用工具**:执行工具获取信息
|
|
||||||
5. **整合回复**:将工具获取的信息整合到回复中
|
|
||||||
|
|
||||||
### 使用示例
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 用户消息示例
|
|
||||||
"今天北京的天气怎么样?"
|
|
||||||
|
|
||||||
# 系统处理流程:
|
|
||||||
# 1. 麦麦识别这是天气查询需求
|
|
||||||
# 2. 调用 weather_query 工具
|
|
||||||
# 3. 获取北京天气信息
|
|
||||||
# 4. 整合信息生成回复
|
|
||||||
|
|
||||||
# 最终回复:
|
|
||||||
"根据最新天气数据,北京今天晴天,温度22°C,湿度45%,适合外出活动。"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚨 注意事项和限制
|
## 🚨 注意事项和限制
|
||||||
|
|
||||||
### 当前限制
|
### 当前限制
|
||||||
|
|
||||||
1. **模式限制**:仅在Focus模式下可用
|
1. **适用范围**:主要适用于信息获取场景
|
||||||
2. **独立开发**:需要单独编写,暂未完全融入插件系统
|
2. **配置要求**:必须开启工具处理器
|
||||||
3. **适用范围**:主要适用于信息获取场景
|
|
||||||
4. **配置要求**:必须开启工具处理器
|
|
||||||
|
|
||||||
### 未来改进
|
|
||||||
|
|
||||||
工具系统在之后可能会面临以下修改:
|
|
||||||
|
|
||||||
1. **插件系统融合**:更好地集成到插件系统中
|
|
||||||
2. **模式扩展**:可能扩展到其他聊天模式
|
|
||||||
3. **配置简化**:简化配置和部署流程
|
|
||||||
4. **性能优化**:提升工具调用效率
|
|
||||||
|
|
||||||
### 开发建议
|
### 开发建议
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from typing import List, Tuple, Type
|
from typing import List, Tuple, Type, Any
|
||||||
from src.plugin_system import (
|
from src.plugin_system import (
|
||||||
BasePlugin,
|
BasePlugin,
|
||||||
register_plugin,
|
register_plugin,
|
||||||
BaseAction,
|
BaseAction,
|
||||||
BaseCommand,
|
BaseCommand,
|
||||||
|
BaseTool,
|
||||||
ComponentInfo,
|
ComponentInfo,
|
||||||
ActionActivationType,
|
ActionActivationType,
|
||||||
ConfigField,
|
ConfigField,
|
||||||
@@ -13,6 +14,45 @@ from src.plugin_system import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CompareNumbersTool(BaseTool):
|
||||||
|
"""比较两个数大小的工具"""
|
||||||
|
|
||||||
|
name = "compare_numbers"
|
||||||
|
description = "使用工具 比较两个数的大小,返回较大的数"
|
||||||
|
parameters = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"num1": {"type": "number", "description": "第一个数字"},
|
||||||
|
"num2": {"type": "number", "description": "第二个数字"},
|
||||||
|
},
|
||||||
|
"required": ["num1", "num2"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""执行比较两个数的大小
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_args: 工具参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 工具执行结果
|
||||||
|
"""
|
||||||
|
num1: int | float = function_args.get("num1") # type: ignore
|
||||||
|
num2: int | float = function_args.get("num2") # type: ignore
|
||||||
|
|
||||||
|
try:
|
||||||
|
if num1 > num2:
|
||||||
|
result = f"{num1} 大于 {num2}"
|
||||||
|
elif num1 < num2:
|
||||||
|
result = f"{num1} 小于 {num2}"
|
||||||
|
else:
|
||||||
|
result = f"{num1} 等于 {num2}"
|
||||||
|
|
||||||
|
return {"name": self.name, "content": result}
|
||||||
|
except Exception as e:
|
||||||
|
return {"name": self.name, "content": f"比较数字失败,炸了: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
# ===== Action组件 =====
|
# ===== Action组件 =====
|
||||||
class HelloAction(BaseAction):
|
class HelloAction(BaseAction):
|
||||||
"""问候Action - 简单的问候动作"""
|
"""问候Action - 简单的问候动作"""
|
||||||
@@ -132,7 +172,9 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||||
},
|
},
|
||||||
"greeting": {
|
"greeting": {
|
||||||
"message": ConfigField(type=str, default="嗨!很开心见到你!😊", description="默认问候消息"),
|
"message": ConfigField(
|
||||||
|
type=list, default=["嗨!很开心见到你!😊", "Ciallo~(∠・ω< )⌒★"], description="默认问候消息"
|
||||||
|
),
|
||||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
|
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
|
||||||
},
|
},
|
||||||
"time": {"format": ConfigField(type=str, default="%Y-%m-%d %H:%M:%S", description="时间显示格式")},
|
"time": {"format": ConfigField(type=str, default="%Y-%m-%d %H:%M:%S", description="时间显示格式")},
|
||||||
@@ -142,6 +184,7 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||||
return [
|
return [
|
||||||
(HelloAction.get_action_info(), HelloAction),
|
(HelloAction.get_action_info(), HelloAction),
|
||||||
|
(CompareNumbersTool.get_tool_info(), CompareNumbersTool), # 添加比较数字工具
|
||||||
(ByeAction.get_action_info(), ByeAction), # 添加告别Action
|
(ByeAction.get_action_info(), ByeAction), # 添加告别Action
|
||||||
(TimeCommand.get_command_info(), TimeCommand),
|
(TimeCommand.get_command_info(), TimeCommand),
|
||||||
(PrintMessage.get_handler_info(), PrintMessage),
|
(PrintMessage.get_handler_info(), PrintMessage),
|
||||||
|
|||||||
@@ -1425,3 +1425,4 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
394
scripts/text_length_analysis.py
Normal file
394
scripts/text_length_analysis.py
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from typing import Dict, List, Tuple, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
# Add project root to Python path
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
sys.path.insert(0, project_root)
|
||||||
|
from src.common.database.database_model import Messages, ChatStreams #noqa
|
||||||
|
|
||||||
|
|
||||||
|
def contains_emoji_or_image_tags(text: str) -> bool:
|
||||||
|
"""Check if text contains [表情包xxxxx] or [图片xxxxx] tags"""
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查是否包含 [表情包] 或 [图片] 标记
|
||||||
|
emoji_pattern = r'\[表情包[^\]]*\]'
|
||||||
|
image_pattern = r'\[图片[^\]]*\]'
|
||||||
|
|
||||||
|
return bool(re.search(emoji_pattern, text) or re.search(image_pattern, text))
|
||||||
|
|
||||||
|
|
||||||
|
def clean_reply_text(text: str) -> str:
|
||||||
|
"""Remove reply references like [回复 xxxx...] from text"""
|
||||||
|
if not text:
|
||||||
|
return text
|
||||||
|
|
||||||
|
# 匹配 [回复 xxxx...] 格式的内容
|
||||||
|
# 使用非贪婪匹配,匹配到第一个 ] 就停止
|
||||||
|
cleaned_text = re.sub(r'\[回复[^\]]*\]', '', text)
|
||||||
|
|
||||||
|
# 去除多余的空白字符
|
||||||
|
cleaned_text = cleaned_text.strip()
|
||||||
|
|
||||||
|
return cleaned_text
|
||||||
|
|
||||||
|
|
||||||
|
def get_chat_name(chat_id: str) -> str:
|
||||||
|
"""Get chat name from chat_id by querying ChatStreams table directly"""
|
||||||
|
try:
|
||||||
|
chat_stream = ChatStreams.get_or_none(ChatStreams.stream_id == chat_id)
|
||||||
|
if chat_stream is None:
|
||||||
|
return f"未知聊天 ({chat_id})"
|
||||||
|
|
||||||
|
if chat_stream.group_name:
|
||||||
|
return f"{chat_stream.group_name} ({chat_id})"
|
||||||
|
elif chat_stream.user_nickname:
|
||||||
|
return f"{chat_stream.user_nickname}的私聊 ({chat_id})"
|
||||||
|
else:
|
||||||
|
return f"未知聊天 ({chat_id})"
|
||||||
|
except Exception:
|
||||||
|
return f"查询失败 ({chat_id})"
|
||||||
|
|
||||||
|
|
||||||
|
def format_timestamp(timestamp: float) -> str:
|
||||||
|
"""Format timestamp to readable date string"""
|
||||||
|
try:
|
||||||
|
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
except (ValueError, OSError):
|
||||||
|
return "未知时间"
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_text_length_distribution(messages) -> Dict[str, int]:
|
||||||
|
"""Calculate distribution of processed_plain_text length"""
|
||||||
|
distribution = {
|
||||||
|
'0': 0, # 空文本
|
||||||
|
'1-5': 0, # 极短文本
|
||||||
|
'6-10': 0, # 很短文本
|
||||||
|
'11-20': 0, # 短文本
|
||||||
|
'21-30': 0, # 较短文本
|
||||||
|
'31-50': 0, # 中短文本
|
||||||
|
'51-70': 0, # 中等文本
|
||||||
|
'71-100': 0, # 较长文本
|
||||||
|
'101-150': 0, # 长文本
|
||||||
|
'151-200': 0, # 很长文本
|
||||||
|
'201-300': 0, # 超长文本
|
||||||
|
'301-500': 0, # 极长文本
|
||||||
|
'501-1000': 0, # 巨长文本
|
||||||
|
'1000+': 0 # 超巨长文本
|
||||||
|
}
|
||||||
|
|
||||||
|
for msg in messages:
|
||||||
|
if msg.processed_plain_text is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 排除包含表情包或图片标记的消息
|
||||||
|
if contains_emoji_or_image_tags(msg.processed_plain_text):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 清理文本中的回复引用
|
||||||
|
cleaned_text = clean_reply_text(msg.processed_plain_text)
|
||||||
|
length = len(cleaned_text)
|
||||||
|
|
||||||
|
if length == 0:
|
||||||
|
distribution['0'] += 1
|
||||||
|
elif length <= 5:
|
||||||
|
distribution['1-5'] += 1
|
||||||
|
elif length <= 10:
|
||||||
|
distribution['6-10'] += 1
|
||||||
|
elif length <= 20:
|
||||||
|
distribution['11-20'] += 1
|
||||||
|
elif length <= 30:
|
||||||
|
distribution['21-30'] += 1
|
||||||
|
elif length <= 50:
|
||||||
|
distribution['31-50'] += 1
|
||||||
|
elif length <= 70:
|
||||||
|
distribution['51-70'] += 1
|
||||||
|
elif length <= 100:
|
||||||
|
distribution['71-100'] += 1
|
||||||
|
elif length <= 150:
|
||||||
|
distribution['101-150'] += 1
|
||||||
|
elif length <= 200:
|
||||||
|
distribution['151-200'] += 1
|
||||||
|
elif length <= 300:
|
||||||
|
distribution['201-300'] += 1
|
||||||
|
elif length <= 500:
|
||||||
|
distribution['301-500'] += 1
|
||||||
|
elif length <= 1000:
|
||||||
|
distribution['501-1000'] += 1
|
||||||
|
else:
|
||||||
|
distribution['1000+'] += 1
|
||||||
|
|
||||||
|
return distribution
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_length_stats(messages) -> Dict[str, float]:
|
||||||
|
"""Calculate basic statistics for processed_plain_text length"""
|
||||||
|
lengths = []
|
||||||
|
null_count = 0
|
||||||
|
excluded_count = 0 # 被排除的消息数量
|
||||||
|
|
||||||
|
for msg in messages:
|
||||||
|
if msg.processed_plain_text is None:
|
||||||
|
null_count += 1
|
||||||
|
elif contains_emoji_or_image_tags(msg.processed_plain_text):
|
||||||
|
# 排除包含表情包或图片标记的消息
|
||||||
|
excluded_count += 1
|
||||||
|
else:
|
||||||
|
# 清理文本中的回复引用
|
||||||
|
cleaned_text = clean_reply_text(msg.processed_plain_text)
|
||||||
|
lengths.append(len(cleaned_text))
|
||||||
|
|
||||||
|
if not lengths:
|
||||||
|
return {
|
||||||
|
'count': 0,
|
||||||
|
'null_count': null_count,
|
||||||
|
'excluded_count': excluded_count,
|
||||||
|
'min': 0,
|
||||||
|
'max': 0,
|
||||||
|
'avg': 0,
|
||||||
|
'median': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
lengths.sort()
|
||||||
|
count = len(lengths)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'count': count,
|
||||||
|
'null_count': null_count,
|
||||||
|
'excluded_count': excluded_count,
|
||||||
|
'min': min(lengths),
|
||||||
|
'max': max(lengths),
|
||||||
|
'avg': sum(lengths) / count,
|
||||||
|
'median': lengths[count // 2] if count % 2 == 1 else (lengths[count // 2 - 1] + lengths[count // 2]) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_chats() -> List[Tuple[str, str, int]]:
|
||||||
|
"""Get all available chats with message counts"""
|
||||||
|
try:
|
||||||
|
# 获取所有有消息的chat_id,排除特殊类型消息
|
||||||
|
chat_counts = {}
|
||||||
|
for msg in Messages.select(Messages.chat_id).distinct():
|
||||||
|
chat_id = msg.chat_id
|
||||||
|
count = Messages.select().where(
|
||||||
|
(Messages.chat_id == chat_id) &
|
||||||
|
(Messages.is_emoji != 1) &
|
||||||
|
(Messages.is_picid != 1) &
|
||||||
|
(Messages.is_command != 1)
|
||||||
|
).count()
|
||||||
|
if count > 0:
|
||||||
|
chat_counts[chat_id] = count
|
||||||
|
|
||||||
|
# 获取聊天名称
|
||||||
|
result = []
|
||||||
|
for chat_id, count in chat_counts.items():
|
||||||
|
chat_name = get_chat_name(chat_id)
|
||||||
|
result.append((chat_id, chat_name, count))
|
||||||
|
|
||||||
|
# 按消息数量排序
|
||||||
|
result.sort(key=lambda x: x[2], reverse=True)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"获取聊天列表失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_time_range_input() -> Tuple[Optional[float], Optional[float]]:
|
||||||
|
"""Get time range input from user"""
|
||||||
|
print("\n时间范围选择:")
|
||||||
|
print("1. 最近1天")
|
||||||
|
print("2. 最近3天")
|
||||||
|
print("3. 最近7天")
|
||||||
|
print("4. 最近30天")
|
||||||
|
print("5. 自定义时间范围")
|
||||||
|
print("6. 不限制时间")
|
||||||
|
|
||||||
|
choice = input("请选择时间范围 (1-6): ").strip()
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
if choice == "1":
|
||||||
|
return now - 24*3600, now
|
||||||
|
elif choice == "2":
|
||||||
|
return now - 3*24*3600, now
|
||||||
|
elif choice == "3":
|
||||||
|
return now - 7*24*3600, now
|
||||||
|
elif choice == "4":
|
||||||
|
return now - 30*24*3600, now
|
||||||
|
elif choice == "5":
|
||||||
|
print("请输入开始时间 (格式: YYYY-MM-DD HH:MM:SS):")
|
||||||
|
start_str = input().strip()
|
||||||
|
print("请输入结束时间 (格式: YYYY-MM-DD HH:MM:SS):")
|
||||||
|
end_str = input().strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_time = datetime.strptime(start_str, "%Y-%m-%d %H:%M:%S").timestamp()
|
||||||
|
end_time = datetime.strptime(end_str, "%Y-%m-%d %H:%M:%S").timestamp()
|
||||||
|
return start_time, end_time
|
||||||
|
except ValueError:
|
||||||
|
print("时间格式错误,将不限制时间范围")
|
||||||
|
return None, None
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_top_longest_messages(messages, top_n: int = 10) -> List[Tuple[str, int, str, str]]:
|
||||||
|
"""Get top N longest messages"""
|
||||||
|
message_lengths = []
|
||||||
|
|
||||||
|
for msg in messages:
|
||||||
|
if msg.processed_plain_text is not None:
|
||||||
|
# 排除包含表情包或图片标记的消息
|
||||||
|
if contains_emoji_or_image_tags(msg.processed_plain_text):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 清理文本中的回复引用
|
||||||
|
cleaned_text = clean_reply_text(msg.processed_plain_text)
|
||||||
|
length = len(cleaned_text)
|
||||||
|
chat_name = get_chat_name(msg.chat_id)
|
||||||
|
time_str = format_timestamp(msg.time)
|
||||||
|
# 截取前100个字符作为预览
|
||||||
|
preview = cleaned_text[:100] + "..." if len(cleaned_text) > 100 else cleaned_text
|
||||||
|
message_lengths.append((chat_name, length, time_str, preview))
|
||||||
|
|
||||||
|
# 按长度排序,取前N个
|
||||||
|
message_lengths.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
return message_lengths[:top_n]
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_text_lengths(chat_id: Optional[str] = None, start_time: Optional[float] = None, end_time: Optional[float] = None) -> None:
|
||||||
|
"""Analyze processed_plain_text lengths with optional filters"""
|
||||||
|
|
||||||
|
# 构建查询条件,排除特殊类型的消息
|
||||||
|
query = Messages.select().where(
|
||||||
|
(Messages.is_emoji != 1) &
|
||||||
|
(Messages.is_picid != 1) &
|
||||||
|
(Messages.is_command != 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
if chat_id:
|
||||||
|
query = query.where(Messages.chat_id == chat_id)
|
||||||
|
|
||||||
|
if start_time:
|
||||||
|
query = query.where(Messages.time >= start_time)
|
||||||
|
|
||||||
|
if end_time:
|
||||||
|
query = query.where(Messages.time <= end_time)
|
||||||
|
|
||||||
|
messages = list(query)
|
||||||
|
|
||||||
|
if not messages:
|
||||||
|
print("没有找到符合条件的消息")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 计算统计信息
|
||||||
|
distribution = calculate_text_length_distribution(messages)
|
||||||
|
stats = get_text_length_stats(messages)
|
||||||
|
top_longest = get_top_longest_messages(messages, 10)
|
||||||
|
|
||||||
|
# 显示结果
|
||||||
|
print("\n=== Processed Plain Text 长度分析结果 ===")
|
||||||
|
print("(已排除表情、图片ID、命令类型消息,已排除[表情包]和[图片]标记消息,已清理回复引用)")
|
||||||
|
if chat_id:
|
||||||
|
print(f"聊天: {get_chat_name(chat_id)}")
|
||||||
|
else:
|
||||||
|
print("聊天: 全部聊天")
|
||||||
|
|
||||||
|
if start_time and end_time:
|
||||||
|
print(f"时间范围: {format_timestamp(start_time)} 到 {format_timestamp(end_time)}")
|
||||||
|
elif start_time:
|
||||||
|
print(f"时间范围: {format_timestamp(start_time)} 之后")
|
||||||
|
elif end_time:
|
||||||
|
print(f"时间范围: {format_timestamp(end_time)} 之前")
|
||||||
|
else:
|
||||||
|
print("时间范围: 不限制")
|
||||||
|
|
||||||
|
print("\n基本统计:")
|
||||||
|
print(f"总消息数量: {len(messages)}")
|
||||||
|
print(f"有文本消息数量: {stats['count']}")
|
||||||
|
print(f"空文本消息数量: {stats['null_count']}")
|
||||||
|
print(f"被排除的消息数量: {stats['excluded_count']}")
|
||||||
|
if stats['count'] > 0:
|
||||||
|
print(f"最短长度: {stats['min']} 字符")
|
||||||
|
print(f"最长长度: {stats['max']} 字符")
|
||||||
|
print(f"平均长度: {stats['avg']:.2f} 字符")
|
||||||
|
print(f"中位数长度: {stats['median']:.2f} 字符")
|
||||||
|
|
||||||
|
print("\n文本长度分布:")
|
||||||
|
total = stats['count']
|
||||||
|
if total > 0:
|
||||||
|
for range_name, count in distribution.items():
|
||||||
|
if count > 0:
|
||||||
|
percentage = count / total * 100
|
||||||
|
print(f"{range_name} 字符: {count} ({percentage:.2f}%)")
|
||||||
|
|
||||||
|
# 显示最长的消息
|
||||||
|
if top_longest:
|
||||||
|
print(f"\n最长的 {len(top_longest)} 条消息:")
|
||||||
|
for i, (chat_name, length, time_str, preview) in enumerate(top_longest, 1):
|
||||||
|
print(f"{i}. [{chat_name}] {time_str}")
|
||||||
|
print(f" 长度: {length} 字符")
|
||||||
|
print(f" 预览: {preview}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def interactive_menu() -> None:
|
||||||
|
"""Interactive menu for text length analysis"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("Processed Plain Text 长度分析工具")
|
||||||
|
print("="*50)
|
||||||
|
print("1. 分析全部聊天")
|
||||||
|
print("2. 选择特定聊天分析")
|
||||||
|
print("q. 退出")
|
||||||
|
|
||||||
|
choice = input("\n请选择分析模式 (1-2, q): ").strip()
|
||||||
|
|
||||||
|
if choice.lower() == 'q':
|
||||||
|
print("再见!")
|
||||||
|
break
|
||||||
|
|
||||||
|
chat_id = None
|
||||||
|
|
||||||
|
if choice == "2":
|
||||||
|
# 显示可用的聊天列表
|
||||||
|
chats = get_available_chats()
|
||||||
|
if not chats:
|
||||||
|
print("没有找到聊天数据")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"\n可用的聊天 (共{len(chats)}个):")
|
||||||
|
for i, (_cid, name, count) in enumerate(chats, 1):
|
||||||
|
print(f"{i}. {name} ({count}条消息)")
|
||||||
|
|
||||||
|
try:
|
||||||
|
chat_choice = int(input(f"\n请选择聊天 (1-{len(chats)}): ").strip())
|
||||||
|
if 1 <= chat_choice <= len(chats):
|
||||||
|
chat_id = chats[chat_choice - 1][0]
|
||||||
|
else:
|
||||||
|
print("无效选择")
|
||||||
|
continue
|
||||||
|
except ValueError:
|
||||||
|
print("请输入有效数字")
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif choice != "1":
|
||||||
|
print("无效选择")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 获取时间范围
|
||||||
|
start_time, end_time = get_time_range_input()
|
||||||
|
|
||||||
|
# 执行分析
|
||||||
|
analyze_text_lengths(chat_id, start_time, end_time)
|
||||||
|
|
||||||
|
input("\n按回车键继续...")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
interactive_menu()
|
||||||
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import random
|
import random
|
||||||
from typing import List, Optional, Dict, Any
|
from typing import List, Optional, Dict, Any, Tuple
|
||||||
from rich.traceback import install
|
from rich.traceback import install
|
||||||
|
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
@@ -18,11 +18,12 @@ from src.chat.chat_loop.hfc_utils import CycleDetail
|
|||||||
from src.person_info.relationship_builder_manager import relationship_builder_manager
|
from src.person_info.relationship_builder_manager import relationship_builder_manager
|
||||||
from src.person_info.person_info import get_person_info_manager
|
from src.person_info.person_info import get_person_info_manager
|
||||||
from src.plugin_system.base.component_types import ActionInfo, ChatMode
|
from src.plugin_system.base.component_types import ActionInfo, ChatMode
|
||||||
from src.plugin_system.apis import generator_api, send_api, message_api
|
from src.plugin_system.apis import generator_api, send_api, message_api, database_api
|
||||||
from src.chat.willing.willing_manager import get_willing_manager
|
from src.chat.willing.willing_manager import get_willing_manager
|
||||||
from src.mais4u.mai_think import mai_thinking_manager
|
from src.mais4u.mai_think import mai_thinking_manager
|
||||||
from maim_message.message_base import GroupInfo
|
|
||||||
from src.mais4u.constant_s4u import ENABLE_S4U
|
from src.mais4u.constant_s4u import ENABLE_S4U
|
||||||
|
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
||||||
|
from src.chat.chat_loop.hfc_utils import send_typing, stop_typing
|
||||||
|
|
||||||
ERROR_LOOP_INFO = {
|
ERROR_LOOP_INFO = {
|
||||||
"loop_plan_info": {
|
"loop_plan_info": {
|
||||||
@@ -88,11 +89,6 @@ class HeartFChatting:
|
|||||||
|
|
||||||
self.loop_mode = ChatMode.NORMAL # 初始循环模式为普通模式
|
self.loop_mode = ChatMode.NORMAL # 初始循环模式为普通模式
|
||||||
|
|
||||||
# 新增:消息计数器和疲惫阈值
|
|
||||||
self._message_count = 0 # 发送的消息计数
|
|
||||||
self._message_threshold = max(10, int(30 * global_config.chat.focus_value))
|
|
||||||
self._fatigue_triggered = False # 是否已触发疲惫退出
|
|
||||||
|
|
||||||
self.action_manager = ActionManager()
|
self.action_manager = ActionManager()
|
||||||
self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager)
|
self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager)
|
||||||
self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id)
|
self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id)
|
||||||
@@ -112,7 +108,6 @@ class HeartFChatting:
|
|||||||
|
|
||||||
self.last_read_time = time.time() - 1
|
self.last_read_time = time.time() - 1
|
||||||
|
|
||||||
self.willing_amplifier = 1
|
|
||||||
self.willing_manager = get_willing_manager()
|
self.willing_manager = get_willing_manager()
|
||||||
|
|
||||||
logger.info(f"{self.log_prefix} HeartFChatting 初始化完成")
|
logger.info(f"{self.log_prefix} HeartFChatting 初始化完成")
|
||||||
@@ -182,6 +177,9 @@ class HeartFChatting:
|
|||||||
if self.loop_mode == ChatMode.NORMAL:
|
if self.loop_mode == ChatMode.NORMAL:
|
||||||
self.energy_value -= 0.3
|
self.energy_value -= 0.3
|
||||||
self.energy_value = max(self.energy_value, 0.3)
|
self.energy_value = max(self.energy_value, 0.3)
|
||||||
|
if self.loop_mode == ChatMode.FOCUS:
|
||||||
|
self.energy_value -= 0.6
|
||||||
|
self.energy_value = max(self.energy_value, 0.3)
|
||||||
|
|
||||||
def print_cycle_info(self, cycle_timers):
|
def print_cycle_info(self, cycle_timers):
|
||||||
# 记录循环信息和计时器结果
|
# 记录循环信息和计时器结果
|
||||||
@@ -200,9 +198,9 @@ class HeartFChatting:
|
|||||||
async def _loopbody(self):
|
async def _loopbody(self):
|
||||||
if self.loop_mode == ChatMode.FOCUS:
|
if self.loop_mode == ChatMode.FOCUS:
|
||||||
if await self._observe():
|
if await self._observe():
|
||||||
self.energy_value -= 1 * global_config.chat.focus_value
|
self.energy_value -= 1 / global_config.chat.focus_value
|
||||||
else:
|
else:
|
||||||
self.energy_value -= 3 * global_config.chat.focus_value
|
self.energy_value -= 3 / global_config.chat.focus_value
|
||||||
if self.energy_value <= 1:
|
if self.energy_value <= 1:
|
||||||
self.energy_value = 1
|
self.energy_value = 1
|
||||||
self.loop_mode = ChatMode.NORMAL
|
self.loop_mode = ChatMode.NORMAL
|
||||||
@@ -218,15 +216,17 @@ class HeartFChatting:
|
|||||||
limit_mode="earliest",
|
limit_mode="earliest",
|
||||||
filter_bot=True,
|
filter_bot=True,
|
||||||
)
|
)
|
||||||
|
if global_config.chat.focus_value != 0:
|
||||||
|
if len(new_messages_data) > 3 / pow(global_config.chat.focus_value, 0.5):
|
||||||
|
self.loop_mode = ChatMode.FOCUS
|
||||||
|
self.energy_value = (
|
||||||
|
10 + (len(new_messages_data) / (3 / pow(global_config.chat.focus_value, 0.5))) * 10
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
if len(new_messages_data) > 3 * global_config.chat.focus_value:
|
if self.energy_value >= 30:
|
||||||
self.loop_mode = ChatMode.FOCUS
|
self.loop_mode = ChatMode.FOCUS
|
||||||
self.energy_value = 10 + (len(new_messages_data) / (3 * global_config.chat.focus_value)) * 10
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
if self.energy_value >= 30 * global_config.chat.focus_value:
|
|
||||||
self.loop_mode = ChatMode.FOCUS
|
|
||||||
return True
|
|
||||||
|
|
||||||
if new_messages_data:
|
if new_messages_data:
|
||||||
earliest_messages_data = new_messages_data[0]
|
earliest_messages_data = new_messages_data[0]
|
||||||
@@ -235,10 +235,10 @@ class HeartFChatting:
|
|||||||
if_think = await self.normal_response(earliest_messages_data)
|
if_think = await self.normal_response(earliest_messages_data)
|
||||||
if if_think:
|
if if_think:
|
||||||
factor = max(global_config.chat.focus_value, 0.1)
|
factor = max(global_config.chat.focus_value, 0.1)
|
||||||
self.energy_value *= 1.1 / factor
|
self.energy_value *= 1.1 * factor
|
||||||
logger.info(f"{self.log_prefix} 进行了思考,能量值按倍数增加,当前能量值:{self.energy_value:.1f}")
|
logger.info(f"{self.log_prefix} 进行了思考,能量值按倍数增加,当前能量值:{self.energy_value:.1f}")
|
||||||
else:
|
else:
|
||||||
self.energy_value += 0.1 / global_config.chat.focus_value
|
self.energy_value += 0.1 * global_config.chat.focus_value
|
||||||
logger.debug(f"{self.log_prefix} 没有进行思考,能量值线性增加,当前能量值:{self.energy_value:.1f}")
|
logger.debug(f"{self.log_prefix} 没有进行思考,能量值线性增加,当前能量值:{self.energy_value:.1f}")
|
||||||
|
|
||||||
logger.debug(f"{self.log_prefix} 当前能量值:{self.energy_value:.1f}")
|
logger.debug(f"{self.log_prefix} 当前能量值:{self.energy_value:.1f}")
|
||||||
@@ -257,44 +257,69 @@ class HeartFChatting:
|
|||||||
person_name = await person_info_manager.get_value(person_id, "person_name")
|
person_name = await person_info_manager.get_value(person_id, "person_name")
|
||||||
return f"{person_name}:{message_data.get('processed_plain_text')}"
|
return f"{person_name}:{message_data.get('processed_plain_text')}"
|
||||||
|
|
||||||
async def send_typing(self):
|
async def _send_and_store_reply(
|
||||||
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
self,
|
||||||
|
response_set,
|
||||||
|
reply_to_str,
|
||||||
|
loop_start_time,
|
||||||
|
action_message,
|
||||||
|
cycle_timers: Dict[str, float],
|
||||||
|
thinking_id,
|
||||||
|
plan_result,
|
||||||
|
) -> Tuple[Dict[str, Any], str, Dict[str, float]]:
|
||||||
|
with Timer("回复发送", cycle_timers):
|
||||||
|
reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, action_message)
|
||||||
|
|
||||||
chat = await get_chat_manager().get_or_create_stream(
|
# 存储reply action信息
|
||||||
platform="amaidesu_default",
|
person_info_manager = get_person_info_manager()
|
||||||
user_info=None,
|
person_id = person_info_manager.get_person_id(
|
||||||
group_info=group_info,
|
action_message.get("chat_info_platform", ""),
|
||||||
|
action_message.get("user_id", ""),
|
||||||
|
)
|
||||||
|
person_name = await person_info_manager.get_value(person_id, "person_name")
|
||||||
|
action_prompt_display = f"你对{person_name}进行了回复:{reply_text}"
|
||||||
|
|
||||||
|
await database_api.store_action_info(
|
||||||
|
chat_stream=self.chat_stream,
|
||||||
|
action_build_into_prompt=False,
|
||||||
|
action_prompt_display=action_prompt_display,
|
||||||
|
action_done=True,
|
||||||
|
thinking_id=thinking_id,
|
||||||
|
action_data={"reply_text": reply_text, "reply_to": reply_to_str},
|
||||||
|
action_name="reply",
|
||||||
)
|
)
|
||||||
|
|
||||||
await send_api.custom_to_stream(
|
# 构建循环信息
|
||||||
message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False
|
loop_info: Dict[str, Any] = {
|
||||||
)
|
"loop_plan_info": {
|
||||||
|
"action_result": plan_result.get("action_result", {}),
|
||||||
|
},
|
||||||
|
"loop_action_info": {
|
||||||
|
"action_taken": True,
|
||||||
|
"reply_text": reply_text,
|
||||||
|
"command": "",
|
||||||
|
"taken_time": time.time(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
async def stop_typing(self):
|
return loop_info, reply_text, cycle_timers
|
||||||
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
|
||||||
|
|
||||||
chat = await get_chat_manager().get_or_create_stream(
|
|
||||||
platform="amaidesu_default",
|
|
||||||
user_info=None,
|
|
||||||
group_info=group_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
await send_api.custom_to_stream(
|
|
||||||
message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _observe(self, message_data: Optional[Dict[str, Any]] = None):
|
async def _observe(self, message_data: Optional[Dict[str, Any]] = None):
|
||||||
# sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else
|
# sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else
|
||||||
if not message_data:
|
if not message_data:
|
||||||
message_data = {}
|
message_data = {}
|
||||||
action_type = "no_action"
|
action_type = "no_action"
|
||||||
|
reply_text = "" # 初始化reply_text变量,避免UnboundLocalError
|
||||||
|
gen_task = None # 初始化gen_task变量,避免UnboundLocalError
|
||||||
|
reply_to_str = "" # 初始化reply_to_str变量
|
||||||
|
|
||||||
# 创建新的循环信息
|
# 创建新的循环信息
|
||||||
cycle_timers, thinking_id = self.start_cycle()
|
cycle_timers, thinking_id = self.start_cycle()
|
||||||
|
|
||||||
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]")
|
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]")
|
||||||
|
|
||||||
if ENABLE_S4U:
|
if ENABLE_S4U:
|
||||||
await self.send_typing()
|
await send_typing()
|
||||||
|
|
||||||
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
|
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
|
||||||
loop_start_time = time.time()
|
loop_start_time = time.time()
|
||||||
@@ -310,95 +335,254 @@ class HeartFChatting:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self.log_prefix} 动作修改失败: {e}")
|
logger.error(f"{self.log_prefix} 动作修改失败: {e}")
|
||||||
|
|
||||||
# 如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的)
|
# 检查是否在normal模式下没有可用动作(除了reply相关动作)
|
||||||
|
skip_planner = False
|
||||||
if self.loop_mode == ChatMode.NORMAL:
|
if self.loop_mode == ChatMode.NORMAL:
|
||||||
reply_to_str = await self.build_reply_to_str(message_data)
|
# 过滤掉reply相关的动作,检查是否还有其他动作
|
||||||
gen_task = asyncio.create_task(self._generate_response(message_data, available_actions, reply_to_str))
|
non_reply_actions = {
|
||||||
|
k: v for k, v in available_actions.items() if k not in ["reply", "no_reply", "no_action"]
|
||||||
|
}
|
||||||
|
|
||||||
with Timer("规划器", cycle_timers):
|
if not non_reply_actions:
|
||||||
plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode)
|
skip_planner = True
|
||||||
|
logger.info(f"{self.log_prefix} Normal模式下没有可用动作,直接回复")
|
||||||
|
|
||||||
action_result: dict = plan_result.get("action_result", {}) # type: ignore
|
# 直接设置为reply动作
|
||||||
action_type, action_data, reasoning, is_parallel = (
|
action_type = "reply"
|
||||||
action_result.get("action_type", "error"),
|
reasoning = ""
|
||||||
action_result.get("action_data", {}),
|
action_data = {"loop_start_time": loop_start_time}
|
||||||
action_result.get("reasoning", "未提供理由"),
|
is_parallel = False
|
||||||
action_result.get("is_parallel", True),
|
|
||||||
)
|
|
||||||
|
|
||||||
action_data["loop_start_time"] = loop_start_time
|
# 构建plan_result用于后续处理
|
||||||
|
plan_result = {
|
||||||
|
"action_result": {
|
||||||
|
"action_type": action_type,
|
||||||
|
"action_data": action_data,
|
||||||
|
"reasoning": reasoning,
|
||||||
|
"timestamp": time.time(),
|
||||||
|
"is_parallel": is_parallel,
|
||||||
|
},
|
||||||
|
"action_prompt": "",
|
||||||
|
}
|
||||||
|
target_message = message_data
|
||||||
|
|
||||||
if self.loop_mode == ChatMode.NORMAL:
|
# 如果normal模式且不跳过规划器,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的)
|
||||||
if action_type == "no_action":
|
if not skip_planner:
|
||||||
logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复")
|
reply_to_str = await self.build_reply_to_str(message_data)
|
||||||
elif is_parallel:
|
gen_task = asyncio.create_task(
|
||||||
logger.info(
|
self._generate_response(
|
||||||
f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作"
|
message_data=message_data,
|
||||||
|
available_actions=available_actions,
|
||||||
|
reply_to=reply_to_str,
|
||||||
|
request_type="chat.replyer.normal",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定执行{action_type}动作")
|
|
||||||
|
|
||||||
if action_type == "no_action":
|
if not skip_planner:
|
||||||
|
with Timer("规划器", cycle_timers):
|
||||||
|
plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode)
|
||||||
|
|
||||||
|
action_result: Dict[str, Any] = plan_result.get("action_result", {}) # type: ignore
|
||||||
|
action_type, action_data, reasoning, is_parallel = (
|
||||||
|
action_result.get("action_type", "error"),
|
||||||
|
action_result.get("action_data", {}),
|
||||||
|
action_result.get("reasoning", "未提供理由"),
|
||||||
|
action_result.get("is_parallel", True),
|
||||||
|
)
|
||||||
|
|
||||||
|
action_data["loop_start_time"] = loop_start_time
|
||||||
|
|
||||||
|
if action_type == "reply":
|
||||||
|
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复")
|
||||||
|
elif is_parallel:
|
||||||
|
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作")
|
||||||
|
else:
|
||||||
|
# 只有在gen_task存在时才进行相关操作
|
||||||
|
if gen_task:
|
||||||
|
if not gen_task.done():
|
||||||
|
gen_task.cancel()
|
||||||
|
logger.debug(f"{self.log_prefix} 已取消预生成的回复任务")
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复,但选择执行{action_type},不发表回复"
|
||||||
|
)
|
||||||
|
elif generation_result := gen_task.result():
|
||||||
|
content = " ".join([item[1] for item in generation_result if item[0] == "text"])
|
||||||
|
logger.debug(f"{self.log_prefix} 预生成的回复任务已完成")
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(f"{self.log_prefix} 预生成的回复任务未生成有效内容")
|
||||||
|
|
||||||
|
action_message: Dict[str, Any] = message_data or target_message # type: ignore
|
||||||
|
if action_type == "reply":
|
||||||
# 等待回复生成完毕
|
# 等待回复生成完毕
|
||||||
gather_timeout = global_config.chat.thinking_timeout
|
if self.loop_mode == ChatMode.NORMAL:
|
||||||
try:
|
# 只有在gen_task存在时才等待
|
||||||
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
|
if not gen_task:
|
||||||
except asyncio.TimeoutError:
|
reply_to_str = await self.build_reply_to_str(message_data)
|
||||||
response_set = None
|
gen_task = asyncio.create_task(
|
||||||
|
self._generate_response(
|
||||||
|
message_data=message_data,
|
||||||
|
available_actions=available_actions,
|
||||||
|
reply_to=reply_to_str,
|
||||||
|
request_type="chat.replyer.normal",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if response_set:
|
gather_timeout = global_config.chat.thinking_timeout
|
||||||
content = " ".join([item[1] for item in response_set if item[0] == "text"])
|
try:
|
||||||
|
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning(f"{self.log_prefix} 回复生成超时>{global_config.chat.thinking_timeout}s,已跳过")
|
||||||
|
response_set = None
|
||||||
|
|
||||||
# 模型炸了,没有回复内容生成
|
# 模型炸了或超时,没有回复内容生成
|
||||||
if not response_set:
|
if not response_set:
|
||||||
logger.warning(f"[{self.log_prefix}] 模型未生成回复内容")
|
logger.warning(f"{self.log_prefix}模型未生成回复内容")
|
||||||
return False
|
return False
|
||||||
elif action_type not in ["no_action"] and not is_parallel:
|
else:
|
||||||
logger.info(
|
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复 (focus模式)")
|
||||||
f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}")
|
# 构建reply_to字符串
|
||||||
|
reply_to_str = await self.build_reply_to_str(action_message)
|
||||||
|
|
||||||
# 发送回复 (不再需要传入 chat)
|
# 生成回复
|
||||||
reply_text = await self._send_response(response_set, reply_to_str, loop_start_time,message_data)
|
with Timer("回复生成", cycle_timers):
|
||||||
|
response_set = await self._generate_response(
|
||||||
|
message_data=action_message,
|
||||||
|
available_actions=available_actions,
|
||||||
|
reply_to=reply_to_str,
|
||||||
|
request_type="chat.replyer.focus",
|
||||||
if ENABLE_S4U:
|
)
|
||||||
await self.stop_typing()
|
|
||||||
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
|
if not response_set:
|
||||||
|
logger.warning(f"{self.log_prefix}模型未生成回复内容")
|
||||||
|
return False
|
||||||
|
|
||||||
|
loop_info, reply_text, cycle_timers = await self._send_and_store_reply(
|
||||||
|
response_set, reply_to_str, loop_start_time, action_message, cycle_timers, thinking_id, plan_result
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
action_message: Dict[str, Any] = message_data or target_message # type: ignore
|
# 并行执行:同时进行回复发送和动作执行
|
||||||
|
# 先置空防止未定义错误
|
||||||
|
background_reply_task = None
|
||||||
|
background_action_task = None
|
||||||
|
# 如果是并行执行且在normal模式下,需要等待预生成的回复任务完成并发送回复
|
||||||
|
if self.loop_mode == ChatMode.NORMAL and is_parallel and gen_task:
|
||||||
|
|
||||||
# 动作执行计时
|
async def handle_reply_task() -> Tuple[Optional[Dict[str, Any]], str, Dict[str, float]]:
|
||||||
with Timer("动作执行", cycle_timers):
|
# 等待预生成的回复任务完成
|
||||||
success, reply_text, command = await self._handle_action(
|
gather_timeout = global_config.chat.thinking_timeout
|
||||||
action_type, reasoning, action_data, cycle_timers, thinking_id, action_message
|
try:
|
||||||
|
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning(
|
||||||
|
f"{self.log_prefix} 并行执行:回复生成超时>{global_config.chat.thinking_timeout}s,已跳过"
|
||||||
|
)
|
||||||
|
return None, "", {}
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消")
|
||||||
|
return None, "", {}
|
||||||
|
|
||||||
|
if not response_set:
|
||||||
|
logger.warning(f"{self.log_prefix} 模型超时或生成回复内容为空")
|
||||||
|
return None, "", {}
|
||||||
|
|
||||||
|
reply_to_str = await self.build_reply_to_str(action_message)
|
||||||
|
loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply(
|
||||||
|
response_set,
|
||||||
|
reply_to_str,
|
||||||
|
loop_start_time,
|
||||||
|
action_message,
|
||||||
|
cycle_timers,
|
||||||
|
thinking_id,
|
||||||
|
plan_result,
|
||||||
|
)
|
||||||
|
return loop_info, reply_text, cycle_timers_reply
|
||||||
|
|
||||||
|
# 执行回复任务并赋值到变量
|
||||||
|
background_reply_task = asyncio.create_task(handle_reply_task())
|
||||||
|
|
||||||
|
# 动作执行任务
|
||||||
|
async def handle_action_task():
|
||||||
|
with Timer("动作执行", cycle_timers):
|
||||||
|
success, reply_text, command = await self._handle_action(
|
||||||
|
action_type, reasoning, action_data, cycle_timers, thinking_id, action_message
|
||||||
|
)
|
||||||
|
return success, reply_text, command
|
||||||
|
|
||||||
|
# 执行动作任务并赋值到变量
|
||||||
|
background_action_task = asyncio.create_task(handle_action_task())
|
||||||
|
|
||||||
|
reply_loop_info = None
|
||||||
|
reply_text_from_reply = ""
|
||||||
|
action_success = False
|
||||||
|
action_reply_text = ""
|
||||||
|
action_command = ""
|
||||||
|
|
||||||
|
# 并行执行所有任务
|
||||||
|
if background_reply_task:
|
||||||
|
results = await asyncio.gather(
|
||||||
|
background_reply_task, background_action_task, return_exceptions=True
|
||||||
)
|
)
|
||||||
|
# 处理回复任务结果
|
||||||
|
reply_result = results[0]
|
||||||
|
if isinstance(reply_result, BaseException):
|
||||||
|
logger.error(f"{self.log_prefix} 回复任务执行异常: {reply_result}")
|
||||||
|
elif reply_result and reply_result[0] is not None:
|
||||||
|
reply_loop_info, reply_text_from_reply, _ = reply_result
|
||||||
|
|
||||||
loop_info = {
|
# 处理动作任务结果
|
||||||
"loop_plan_info": {
|
action_task_result = results[1]
|
||||||
"action_result": plan_result.get("action_result", {}),
|
if isinstance(action_task_result, BaseException):
|
||||||
},
|
logger.error(f"{self.log_prefix} 动作任务执行异常: {action_task_result}")
|
||||||
"loop_action_info": {
|
else:
|
||||||
"action_taken": success,
|
action_success, action_reply_text, action_command = action_task_result
|
||||||
"reply_text": reply_text,
|
else:
|
||||||
"command": command,
|
results = await asyncio.gather(background_action_task, return_exceptions=True)
|
||||||
"taken_time": time.time(),
|
# 只有动作任务
|
||||||
},
|
action_task_result = results[0]
|
||||||
}
|
if isinstance(action_task_result, BaseException):
|
||||||
|
logger.error(f"{self.log_prefix} 动作任务执行异常: {action_task_result}")
|
||||||
|
else:
|
||||||
|
action_success, action_reply_text, action_command = action_task_result
|
||||||
|
|
||||||
if loop_info["loop_action_info"]["command"] == "stop_focus_chat":
|
# 构建最终的循环信息
|
||||||
logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天")
|
if reply_loop_info:
|
||||||
return False
|
# 如果有回复信息,使用回复的loop_info作为基础
|
||||||
# 停止该聊天模式的循环
|
loop_info = reply_loop_info
|
||||||
|
# 更新动作执行信息
|
||||||
|
loop_info["loop_action_info"].update(
|
||||||
|
{
|
||||||
|
"action_taken": action_success,
|
||||||
|
"command": action_command,
|
||||||
|
"taken_time": time.time(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
reply_text = reply_text_from_reply
|
||||||
|
else:
|
||||||
|
# 没有回复信息,构建纯动作的loop_info
|
||||||
|
loop_info = {
|
||||||
|
"loop_plan_info": {
|
||||||
|
"action_result": plan_result.get("action_result", {}),
|
||||||
|
},
|
||||||
|
"loop_action_info": {
|
||||||
|
"action_taken": action_success,
|
||||||
|
"reply_text": action_reply_text,
|
||||||
|
"command": action_command,
|
||||||
|
"taken_time": time.time(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reply_text = action_reply_text
|
||||||
|
|
||||||
|
if ENABLE_S4U:
|
||||||
|
await stop_typing()
|
||||||
|
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
|
||||||
|
|
||||||
self.end_cycle(loop_info, cycle_timers)
|
self.end_cycle(loop_info, cycle_timers)
|
||||||
self.print_cycle_info(cycle_timers)
|
self.print_cycle_info(cycle_timers)
|
||||||
@@ -406,8 +590,16 @@ class HeartFChatting:
|
|||||||
if self.loop_mode == ChatMode.NORMAL:
|
if self.loop_mode == ChatMode.NORMAL:
|
||||||
await self.willing_manager.after_generate_reply_handle(message_data.get("message_id", ""))
|
await self.willing_manager.after_generate_reply_handle(message_data.get("message_id", ""))
|
||||||
|
|
||||||
|
# 管理no_reply计数器:当执行了非no_reply动作时,重置计数器
|
||||||
if action_type != "no_reply" and action_type != "no_action":
|
if action_type != "no_reply" and action_type != "no_action":
|
||||||
|
# 导入NoReplyAction并重置计数器
|
||||||
|
NoReplyAction.reset_consecutive_count()
|
||||||
|
logger.info(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器")
|
||||||
return True
|
return True
|
||||||
|
elif action_type == "no_action":
|
||||||
|
# 当执行回复动作时,也重置no_reply计数器s
|
||||||
|
NoReplyAction.reset_consecutive_count()
|
||||||
|
logger.info(f"{self.log_prefix} 执行了回复动作,重置no_reply计数器")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -435,7 +627,7 @@ class HeartFChatting:
|
|||||||
action: str,
|
action: str,
|
||||||
reasoning: str,
|
reasoning: str,
|
||||||
action_data: dict,
|
action_data: dict,
|
||||||
cycle_timers: dict,
|
cycle_timers: Dict[str, float],
|
||||||
thinking_id: str,
|
thinking_id: str,
|
||||||
action_message: dict,
|
action_message: dict,
|
||||||
) -> tuple[bool, str, str]:
|
) -> tuple[bool, str, str]:
|
||||||
@@ -501,7 +693,7 @@ class HeartFChatting:
|
|||||||
在"兴趣"模式下,判断是否回复并生成内容。
|
在"兴趣"模式下,判断是否回复并生成内容。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
interested_rate = (message_data.get("interest_value") or 0.0) * self.willing_amplifier
|
interested_rate = (message_data.get("interest_value") or 0.0) * global_config.chat.willing_amplifier
|
||||||
|
|
||||||
self.willing_manager.setup(message_data, self.chat_stream)
|
self.willing_manager.setup(message_data, self.chat_stream)
|
||||||
|
|
||||||
@@ -515,8 +707,8 @@ class HeartFChatting:
|
|||||||
reply_probability += additional_config["maimcore_reply_probability_gain"]
|
reply_probability += additional_config["maimcore_reply_probability_gain"]
|
||||||
reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间
|
reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间
|
||||||
|
|
||||||
talk_frequency = global_config.chat.get_current_talk_frequency(self.stream_id)
|
talk_frequency = global_config.chat.get_current_talk_frequency(self.stream_id)
|
||||||
reply_probability = talk_frequency * reply_probability
|
reply_probability = talk_frequency * reply_probability
|
||||||
|
|
||||||
# 处理表情包
|
# 处理表情包
|
||||||
if message_data.get("is_emoji") or message_data.get("is_picid"):
|
if message_data.get("is_emoji") or message_data.get("is_picid"):
|
||||||
@@ -544,7 +736,11 @@ class HeartFChatting:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def _generate_response(
|
async def _generate_response(
|
||||||
self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str
|
self,
|
||||||
|
message_data: dict,
|
||||||
|
available_actions: Optional[Dict[str, ActionInfo]],
|
||||||
|
reply_to: str,
|
||||||
|
request_type: str = "chat.replyer.normal",
|
||||||
) -> Optional[list]:
|
) -> Optional[list]:
|
||||||
"""生成普通回复"""
|
"""生成普通回复"""
|
||||||
try:
|
try:
|
||||||
@@ -552,8 +748,8 @@ class HeartFChatting:
|
|||||||
chat_stream=self.chat_stream,
|
chat_stream=self.chat_stream,
|
||||||
reply_to=reply_to,
|
reply_to=reply_to,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
enable_tool=global_config.tool.enable_in_normal_chat,
|
enable_tool=global_config.tool.enable_tool,
|
||||||
request_type="chat.replyer.normal",
|
request_type=request_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not success or not reply_set:
|
if not success or not reply_set:
|
||||||
@@ -563,10 +759,10 @@ class HeartFChatting:
|
|||||||
return reply_set
|
return reply_set
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}")
|
logger.error(f"{self.log_prefix}回复生成出现错误:{str(e)} {traceback.format_exc()}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _send_response(self, reply_set, reply_to, thinking_start_time, message_data):
|
async def _send_response(self, reply_set, reply_to, thinking_start_time, message_data) -> str:
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
new_message_count = message_api.count_new_messages(
|
new_message_count = message_api.count_new_messages(
|
||||||
chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time
|
chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time
|
||||||
@@ -578,13 +774,9 @@ class HeartFChatting:
|
|||||||
need_reply = new_message_count >= random.randint(2, 4)
|
need_reply = new_message_count >= random.randint(2, 4)
|
||||||
|
|
||||||
if need_reply:
|
if need_reply:
|
||||||
logger.info(
|
logger.info(f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复")
|
||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.info(f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复")
|
||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
|
|
||||||
)
|
|
||||||
|
|
||||||
reply_text = ""
|
reply_text = ""
|
||||||
first_replied = False
|
first_replied = False
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.common.message_repository import count_messages
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||||
|
from src.plugin_system.apis import send_api
|
||||||
|
from maim_message.message_base import GroupInfo
|
||||||
|
|
||||||
|
from src.common.message_repository import count_messages
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -106,3 +109,30 @@ def get_recent_message_stats(minutes: float = 30, chat_id: Optional[str] = None)
|
|||||||
bot_reply_count = count_messages(bot_filter)
|
bot_reply_count = count_messages(bot_filter)
|
||||||
|
|
||||||
return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count}
|
return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count}
|
||||||
|
|
||||||
|
|
||||||
|
async def send_typing():
|
||||||
|
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
||||||
|
|
||||||
|
chat = await get_chat_manager().get_or_create_stream(
|
||||||
|
platform="amaidesu_default",
|
||||||
|
user_info=None,
|
||||||
|
group_info=group_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
await send_api.custom_to_stream(
|
||||||
|
message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False
|
||||||
|
)
|
||||||
|
|
||||||
|
async def stop_typing():
|
||||||
|
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
||||||
|
|
||||||
|
chat = await get_chat_manager().get_or_create_stream(
|
||||||
|
platform="amaidesu_default",
|
||||||
|
user_info=None,
|
||||||
|
group_info=group_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
await send_api.custom_to_stream(
|
||||||
|
message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False
|
||||||
|
)
|
||||||
@@ -525,9 +525,9 @@ class EmojiManager:
|
|||||||
如果文件已被删除,则执行对象的删除方法并从列表中移除
|
如果文件已被删除,则执行对象的删除方法并从列表中移除
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if not self.emoji_objects:
|
# if not self.emoji_objects:
|
||||||
logger.warning("[检查] emoji_objects为空,跳过完整性检查")
|
# logger.warning("[检查] emoji_objects为空,跳过完整性检查")
|
||||||
return
|
# return
|
||||||
|
|
||||||
total_count = len(self.emoji_objects)
|
total_count = len(self.emoji_objects)
|
||||||
self.emoji_num = total_count
|
self.emoji_num = total_count
|
||||||
@@ -707,6 +707,38 @@ class EmojiManager:
|
|||||||
return emoji
|
return emoji
|
||||||
return None # 如果循环结束还没找到,则返回 None
|
return None # 如果循环结束还没找到,则返回 None
|
||||||
|
|
||||||
|
async def get_emoji_description_by_hash(self, emoji_hash: str) -> Optional[str]:
|
||||||
|
"""根据哈希值获取已注册表情包的描述
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emoji_hash: 表情包的哈希值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[str]: 表情包描述,如果未找到则返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 先从内存中查找
|
||||||
|
emoji = await self.get_emoji_from_manager(emoji_hash)
|
||||||
|
if emoji and emoji.description:
|
||||||
|
logger.info(f"[缓存命中] 从内存获取表情包描述: {emoji.description[:50]}...")
|
||||||
|
return emoji.description
|
||||||
|
|
||||||
|
# 如果内存中没有,从数据库查找
|
||||||
|
self._ensure_db()
|
||||||
|
try:
|
||||||
|
emoji_record = Emoji.get_or_none(Emoji.emoji_hash == emoji_hash)
|
||||||
|
if emoji_record and emoji_record.description:
|
||||||
|
logger.info(f"[缓存命中] 从数据库获取表情包描述: {emoji_record.description[:50]}...")
|
||||||
|
return emoji_record.description
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"从数据库查询表情包描述时出错: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取表情包描述失败 (Hash: {emoji_hash}): {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
async def delete_emoji(self, emoji_hash: str) -> bool:
|
async def delete_emoji(self, emoji_hash: str) -> bool:
|
||||||
"""根据哈希值删除表情包
|
"""根据哈希值删除表情包
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def init_prompt() -> None:
|
|||||||
当"想说明某个具体的事实观点,但懒得明说,或者不便明说,或表达一种默契",使用"懂的都懂"
|
当"想说明某个具体的事实观点,但懒得明说,或者不便明说,或表达一种默契",使用"懂的都懂"
|
||||||
当"当涉及游戏相关时,表示意外的夸赞,略带戏谑意味"时,使用"这么强!"
|
当"当涉及游戏相关时,表示意外的夸赞,略带戏谑意味"时,使用"这么强!"
|
||||||
|
|
||||||
注意不要总结你自己(SELF)的发言
|
请注意:不要总结你自己(SELF)的发言
|
||||||
现在请你概括
|
现在请你概括
|
||||||
"""
|
"""
|
||||||
Prompt(learn_style_prompt, "learn_style_prompt")
|
Prompt(learn_style_prompt, "learn_style_prompt")
|
||||||
@@ -330,48 +330,8 @@ class ExpressionLearner:
|
|||||||
"""
|
"""
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
# 全局衰减所有已存储的表达方式
|
# 全局衰减所有已存储的表达方式(直接操作数据库)
|
||||||
for type in ["style", "grammar"]:
|
self._apply_global_decay_to_database(current_time)
|
||||||
base_dir = os.path.join("data", "expression", f"learnt_{type}")
|
|
||||||
if not os.path.exists(base_dir):
|
|
||||||
logger.debug(f"目录不存在,跳过衰减: {base_dir}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
chat_ids = os.listdir(base_dir)
|
|
||||||
logger.debug(f"在 {base_dir} 中找到 {len(chat_ids)} 个聊天ID目录进行衰减")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"读取目录失败 {base_dir}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
for chat_id in chat_ids:
|
|
||||||
file_path = os.path.join(base_dir, chat_id, "expressions.json")
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(file_path, "r", encoding="utf-8") as f:
|
|
||||||
expressions = json.load(f)
|
|
||||||
|
|
||||||
if not isinstance(expressions, list):
|
|
||||||
logger.warning(f"表达方式文件格式错误,跳过衰减: {file_path}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 应用全局衰减
|
|
||||||
decayed_expressions = self.apply_decay_to_expressions(expressions, current_time)
|
|
||||||
|
|
||||||
# 保存衰减后的结果
|
|
||||||
with open(file_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(decayed_expressions, f, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
logger.debug(f"已对 {file_path} 应用衰减,剩余 {len(decayed_expressions)} 个表达方式")
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
logger.error(f"JSON解析失败,跳过衰减 {file_path}: {e}")
|
|
||||||
except PermissionError as e:
|
|
||||||
logger.error(f"权限不足,无法更新 {file_path}: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"全局衰减{type}表达方式失败 {file_path}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
learnt_style: Optional[List[Tuple[str, str, str]]] = []
|
learnt_style: Optional[List[Tuple[str, str, str]]] = []
|
||||||
learnt_grammar: Optional[List[Tuple[str, str, str]]] = []
|
learnt_grammar: Optional[List[Tuple[str, str, str]]] = []
|
||||||
@@ -388,6 +348,42 @@ class ExpressionLearner:
|
|||||||
|
|
||||||
return learnt_style, learnt_grammar
|
return learnt_style, learnt_grammar
|
||||||
|
|
||||||
|
def _apply_global_decay_to_database(self, current_time: float) -> None:
|
||||||
|
"""
|
||||||
|
对数据库中的所有表达方式应用全局衰减
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取所有表达方式
|
||||||
|
all_expressions = Expression.select()
|
||||||
|
|
||||||
|
updated_count = 0
|
||||||
|
deleted_count = 0
|
||||||
|
|
||||||
|
for expr in all_expressions:
|
||||||
|
# 计算时间差
|
||||||
|
last_active = expr.last_active_time
|
||||||
|
time_diff_days = (current_time - last_active) / (24 * 3600) # 转换为天
|
||||||
|
|
||||||
|
# 计算衰减值
|
||||||
|
decay_value = self.calculate_decay_factor(time_diff_days)
|
||||||
|
new_count = max(0.01, expr.count - decay_value)
|
||||||
|
|
||||||
|
if new_count <= 0.01:
|
||||||
|
# 如果count太小,删除这个表达方式
|
||||||
|
expr.delete_instance()
|
||||||
|
deleted_count += 1
|
||||||
|
else:
|
||||||
|
# 更新count
|
||||||
|
expr.count = new_count
|
||||||
|
expr.save()
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
if updated_count > 0 or deleted_count > 0:
|
||||||
|
logger.info(f"全局衰减完成:更新了 {updated_count} 个表达方式,删除了 {deleted_count} 个表达方式")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"数据库全局衰减失败: {e}")
|
||||||
|
|
||||||
def calculate_decay_factor(self, time_diff_days: float) -> float:
|
def calculate_decay_factor(self, time_diff_days: float) -> float:
|
||||||
"""
|
"""
|
||||||
计算衰减值
|
计算衰减值
|
||||||
@@ -410,30 +406,6 @@ class ExpressionLearner:
|
|||||||
|
|
||||||
return min(0.01, decay)
|
return min(0.01, decay)
|
||||||
|
|
||||||
def apply_decay_to_expressions(
|
|
||||||
self, expressions: List[Dict[str, Any]], current_time: float
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
对表达式列表应用衰减
|
|
||||||
返回衰减后的表达式列表,移除count小于0的项
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
for expr in expressions:
|
|
||||||
# 确保last_active_time存在,如果不存在则使用current_time
|
|
||||||
if "last_active_time" not in expr:
|
|
||||||
expr["last_active_time"] = current_time
|
|
||||||
|
|
||||||
last_active = expr["last_active_time"]
|
|
||||||
time_diff_days = (current_time - last_active) / (24 * 3600) # 转换为天
|
|
||||||
|
|
||||||
decay_value = self.calculate_decay_factor(time_diff_days)
|
|
||||||
expr["count"] = max(0.01, expr.get("count", 1) - decay_value)
|
|
||||||
|
|
||||||
if expr["count"] > 0:
|
|
||||||
result.append(expr)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def learn_and_store(self, type: str, num: int = 10) -> List[Tuple[str, str, str]]:
|
async def learn_and_store(self, type: str, num: int = 10) -> List[Tuple[str, str, str]]:
|
||||||
# sourcery skip: use-join
|
# sourcery skip: use-join
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import json
|
|||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from typing import List, Dict, Tuple, Optional
|
from typing import List, Dict, Tuple, Optional, Any
|
||||||
from json_repair import repair_json
|
from json_repair import repair_json
|
||||||
|
|
||||||
from src.llm_models.utils_model import LLMRequest
|
from src.llm_models.utils_model import LLMRequest
|
||||||
@@ -117,36 +117,42 @@ class ExpressionSelector:
|
|||||||
|
|
||||||
def get_random_expressions(
|
def get_random_expressions(
|
||||||
self, chat_id: str, total_num: int, style_percentage: float, grammar_percentage: float
|
self, chat_id: str, total_num: int, style_percentage: float, grammar_percentage: float
|
||||||
) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
|
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
||||||
# 支持多chat_id合并抽选
|
# 支持多chat_id合并抽选
|
||||||
related_chat_ids = self.get_related_chat_ids(chat_id)
|
related_chat_ids = self.get_related_chat_ids(chat_id)
|
||||||
style_exprs = []
|
|
||||||
grammar_exprs = []
|
# 优化:一次性查询所有相关chat_id的表达方式
|
||||||
for cid in related_chat_ids:
|
style_query = Expression.select().where(
|
||||||
style_query = Expression.select().where((Expression.chat_id == cid) & (Expression.type == "style"))
|
(Expression.chat_id.in_(related_chat_ids)) & (Expression.type == "style")
|
||||||
grammar_query = Expression.select().where((Expression.chat_id == cid) & (Expression.type == "grammar"))
|
)
|
||||||
style_exprs.extend([
|
grammar_query = Expression.select().where(
|
||||||
{
|
(Expression.chat_id.in_(related_chat_ids)) & (Expression.type == "grammar")
|
||||||
"situation": expr.situation,
|
)
|
||||||
"style": expr.style,
|
|
||||||
"count": expr.count,
|
style_exprs = [
|
||||||
"last_active_time": expr.last_active_time,
|
{
|
||||||
"source_id": cid,
|
"situation": expr.situation,
|
||||||
"type": "style",
|
"style": expr.style,
|
||||||
"create_date": expr.create_date if expr.create_date is not None else expr.last_active_time,
|
"count": expr.count,
|
||||||
} for expr in style_query
|
"last_active_time": expr.last_active_time,
|
||||||
])
|
"source_id": expr.chat_id,
|
||||||
grammar_exprs.extend([
|
"type": "style",
|
||||||
{
|
"create_date": expr.create_date if expr.create_date is not None else expr.last_active_time,
|
||||||
"situation": expr.situation,
|
} for expr in style_query
|
||||||
"style": expr.style,
|
]
|
||||||
"count": expr.count,
|
|
||||||
"last_active_time": expr.last_active_time,
|
grammar_exprs = [
|
||||||
"source_id": cid,
|
{
|
||||||
"type": "grammar",
|
"situation": expr.situation,
|
||||||
"create_date": expr.create_date if expr.create_date is not None else expr.last_active_time,
|
"style": expr.style,
|
||||||
} for expr in grammar_query
|
"count": expr.count,
|
||||||
])
|
"last_active_time": expr.last_active_time,
|
||||||
|
"source_id": expr.chat_id,
|
||||||
|
"type": "grammar",
|
||||||
|
"create_date": expr.create_date if expr.create_date is not None else expr.last_active_time,
|
||||||
|
} for expr in grammar_query
|
||||||
|
]
|
||||||
|
|
||||||
style_num = int(total_num * style_percentage)
|
style_num = int(total_num * style_percentage)
|
||||||
grammar_num = int(total_num * grammar_percentage)
|
grammar_num = int(total_num * grammar_percentage)
|
||||||
# 按权重抽样(使用count作为权重)
|
# 按权重抽样(使用count作为权重)
|
||||||
@@ -162,7 +168,7 @@ class ExpressionSelector:
|
|||||||
selected_grammar = []
|
selected_grammar = []
|
||||||
return selected_style, selected_grammar
|
return selected_style, selected_grammar
|
||||||
|
|
||||||
def update_expressions_count_batch(self, expressions_to_update: List[Dict[str, str]], increment: float = 0.1):
|
def update_expressions_count_batch(self, expressions_to_update: List[Dict[str, Any]], increment: float = 0.1):
|
||||||
"""对一批表达方式更新count值,按chat_id+type分组后一次性写入数据库"""
|
"""对一批表达方式更新count值,按chat_id+type分组后一次性写入数据库"""
|
||||||
if not expressions_to_update:
|
if not expressions_to_update:
|
||||||
return
|
return
|
||||||
@@ -203,7 +209,7 @@ class ExpressionSelector:
|
|||||||
max_num: int = 10,
|
max_num: int = 10,
|
||||||
min_num: int = 5,
|
min_num: int = 5,
|
||||||
target_message: Optional[str] = None,
|
target_message: Optional[str] = None,
|
||||||
) -> List[Dict[str, str]]:
|
) -> List[Dict[str, Any]]:
|
||||||
# sourcery skip: inline-variable, list-comprehension
|
# sourcery skip: inline-variable, list-comprehension
|
||||||
"""使用LLM选择适合的表达方式"""
|
"""使用LLM选择适合的表达方式"""
|
||||||
|
|
||||||
@@ -273,6 +279,7 @@ class ExpressionSelector:
|
|||||||
|
|
||||||
if not isinstance(result, dict) or "selected_situations" not in result:
|
if not isinstance(result, dict) or "selected_situations" not in result:
|
||||||
logger.error("LLM返回格式错误")
|
logger.error("LLM返回格式错误")
|
||||||
|
logger.info(f"LLM返回结果: \n{content}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
selected_indices = result["selected_situations"]
|
selected_indices = result["selected_situations"]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from src.chat.message_receive.storage import MessageStorage
|
|||||||
from src.chat.heart_flow.heartflow import heartflow
|
from src.chat.heart_flow.heartflow import heartflow
|
||||||
from src.chat.utils.utils import is_mentioned_bot_in_message
|
from src.chat.utils.utils import is_mentioned_bot_in_message
|
||||||
from src.chat.utils.timer_calculator import Timer
|
from src.chat.utils.timer_calculator import Timer
|
||||||
|
from src.chat.utils.chat_message_builder import replace_user_references_sync
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.person_info.relationship_manager import get_relationship_manager
|
from src.person_info.relationship_manager import get_relationship_manager
|
||||||
from src.mood.mood_manager import mood_manager
|
from src.mood.mood_manager import mood_manager
|
||||||
@@ -56,16 +57,41 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]:
|
|||||||
with Timer("记忆激活"):
|
with Timer("记忆激活"):
|
||||||
interested_rate = await hippocampus_manager.get_activate_from_text(
|
interested_rate = await hippocampus_manager.get_activate_from_text(
|
||||||
message.processed_plain_text,
|
message.processed_plain_text,
|
||||||
|
max_depth= 5,
|
||||||
fast_retrieval=False,
|
fast_retrieval=False,
|
||||||
)
|
)
|
||||||
logger.debug(f"记忆激活率: {interested_rate:.2f}")
|
logger.debug(f"记忆激活率: {interested_rate:.2f}")
|
||||||
|
|
||||||
text_len = len(message.processed_plain_text)
|
text_len = len(message.processed_plain_text)
|
||||||
# 根据文本长度调整兴趣度,长度越大兴趣度越高,但增长率递减,最低0.01,最高0.05
|
# 根据文本长度分布调整兴趣度,采用分段函数实现更精确的兴趣度计算
|
||||||
# 采用对数函数实现递减增长
|
# 基于实际分布:0-5字符(26.57%), 6-10字符(27.18%), 11-20字符(22.76%), 21-30字符(10.33%), 31+字符(13.86%)
|
||||||
|
|
||||||
base_interest = 0.01 + (0.05 - 0.01) * (math.log10(text_len + 1) / math.log10(1000 + 1))
|
if text_len == 0:
|
||||||
base_interest = min(max(base_interest, 0.01), 0.05)
|
base_interest = 0.01 # 空消息最低兴趣度
|
||||||
|
elif text_len <= 5:
|
||||||
|
# 1-5字符:线性增长 0.01 -> 0.03
|
||||||
|
base_interest = 0.01 + (text_len - 1) * (0.03 - 0.01) / 4
|
||||||
|
elif text_len <= 10:
|
||||||
|
# 6-10字符:线性增长 0.03 -> 0.06
|
||||||
|
base_interest = 0.03 + (text_len - 5) * (0.06 - 0.03) / 5
|
||||||
|
elif text_len <= 20:
|
||||||
|
# 11-20字符:线性增长 0.06 -> 0.12
|
||||||
|
base_interest = 0.06 + (text_len - 10) * (0.12 - 0.06) / 10
|
||||||
|
elif text_len <= 30:
|
||||||
|
# 21-30字符:线性增长 0.12 -> 0.18
|
||||||
|
base_interest = 0.12 + (text_len - 20) * (0.18 - 0.12) / 10
|
||||||
|
elif text_len <= 50:
|
||||||
|
# 31-50字符:线性增长 0.18 -> 0.22
|
||||||
|
base_interest = 0.18 + (text_len - 30) * (0.22 - 0.18) / 20
|
||||||
|
elif text_len <= 100:
|
||||||
|
# 51-100字符:线性增长 0.22 -> 0.26
|
||||||
|
base_interest = 0.22 + (text_len - 50) * (0.26 - 0.22) / 50
|
||||||
|
else:
|
||||||
|
# 100+字符:对数增长 0.26 -> 0.3,增长率递减
|
||||||
|
base_interest = 0.26 + (0.3 - 0.26) * (math.log10(text_len - 99) / math.log10(901)) # 1000-99=901
|
||||||
|
|
||||||
|
# 确保在范围内
|
||||||
|
base_interest = min(max(base_interest, 0.01), 0.3)
|
||||||
|
|
||||||
interested_rate += base_interest
|
interested_rate += base_interest
|
||||||
|
|
||||||
@@ -123,8 +149,15 @@ class HeartFCMessageReceiver:
|
|||||||
# 如果消息中包含图片标识,则将 [picid:...] 替换为 [图片]
|
# 如果消息中包含图片标识,则将 [picid:...] 替换为 [图片]
|
||||||
picid_pattern = r"\[picid:([^\]]+)\]"
|
picid_pattern = r"\[picid:([^\]]+)\]"
|
||||||
processed_plain_text = re.sub(picid_pattern, "[图片]", message.processed_plain_text)
|
processed_plain_text = re.sub(picid_pattern, "[图片]", message.processed_plain_text)
|
||||||
|
|
||||||
|
# 应用用户引用格式替换,将回复<aaa:bbb>和@<aaa:bbb>格式转换为可读格式
|
||||||
|
processed_plain_text = replace_user_references_sync(
|
||||||
|
processed_plain_text,
|
||||||
|
message.message_info.platform, # type: ignore
|
||||||
|
replace_bot_name=True
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}") # type: ignore
|
logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}[兴趣度:{interested_rate:.2f}]") # type: ignore
|
||||||
|
|
||||||
logger.debug(f"[{mes_name}][当前时段回复频率: {current_talk_frequency}]")
|
logger.debug(f"[{mes_name}][当前时段回复频率: {current_talk_frequency}]")
|
||||||
|
|
||||||
|
|||||||
@@ -224,10 +224,16 @@ class Hippocampus:
|
|||||||
return hash((source, target))
|
return hash((source, target))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_topic_llm(text, topic_num):
|
def find_topic_llm(text: str, topic_num: int | list[int]):
|
||||||
# sourcery skip: inline-immediately-returned-variable
|
# sourcery skip: inline-immediately-returned-variable
|
||||||
|
topic_num_str = ""
|
||||||
|
if isinstance(topic_num, list):
|
||||||
|
topic_num_str = f"{topic_num[0]}-{topic_num[1]}"
|
||||||
|
else:
|
||||||
|
topic_num_str = topic_num
|
||||||
|
|
||||||
prompt = (
|
prompt = (
|
||||||
f"这是一段文字:\n{text}\n\n请你从这段话中总结出最多{topic_num}个关键的概念,可以是名词,动词,或者特定人物,帮我列出来,"
|
f"这是一段文字:\n{text}\n\n请你从这段话中总结出最多{topic_num_str}个关键的概念,可以是名词,动词,或者特定人物,帮我列出来,"
|
||||||
f"将主题用逗号隔开,并加上<>,例如<主题1>,<主题2>......尽可能精简。只需要列举最多{topic_num}个话题就好,不要有序号,不要告诉我其他内容。"
|
f"将主题用逗号隔开,并加上<>,例如<主题1>,<主题2>......尽可能精简。只需要列举最多{topic_num}个话题就好,不要有序号,不要告诉我其他内容。"
|
||||||
f"如果确定找不出主题或者没有明显主题,返回<none>。"
|
f"如果确定找不出主题或者没有明显主题,返回<none>。"
|
||||||
)
|
)
|
||||||
@@ -300,6 +306,60 @@ class Hippocampus:
|
|||||||
memories.sort(key=lambda x: x[2], reverse=True)
|
memories.sort(key=lambda x: x[2], reverse=True)
|
||||||
return memories
|
return memories
|
||||||
|
|
||||||
|
async def get_keywords_from_text(self, text: str) -> list:
|
||||||
|
"""从文本中提取关键词。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str): 输入文本
|
||||||
|
fast_retrieval (bool, optional): 是否使用快速检索。默认为False。
|
||||||
|
如果为True,使用jieba分词提取关键词,速度更快但可能不够准确。
|
||||||
|
如果为False,使用LLM提取关键词,速度较慢但更准确。
|
||||||
|
"""
|
||||||
|
if not text:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 使用LLM提取关键词 - 根据详细文本长度分布优化topic_num计算
|
||||||
|
text_length = len(text)
|
||||||
|
topic_num: int | list[int] = 0
|
||||||
|
if text_length <= 5:
|
||||||
|
words = jieba.cut(text)
|
||||||
|
keywords = [word for word in words if len(word) > 1]
|
||||||
|
keywords = list(set(keywords))[:3] # 限制最多3个关键词
|
||||||
|
if keywords:
|
||||||
|
logger.info(f"提取关键词: {keywords}")
|
||||||
|
return keywords
|
||||||
|
elif text_length <= 10:
|
||||||
|
topic_num = [1, 3] # 6-10字符: 1个关键词 (27.18%的文本)
|
||||||
|
elif text_length <= 20:
|
||||||
|
topic_num = [2, 4] # 11-20字符: 2个关键词 (22.76%的文本)
|
||||||
|
elif text_length <= 30:
|
||||||
|
topic_num = [3, 5] # 21-30字符: 3个关键词 (10.33%的文本)
|
||||||
|
elif text_length <= 50:
|
||||||
|
topic_num = [4, 5] # 31-50字符: 4个关键词 (9.79%的文本)
|
||||||
|
else:
|
||||||
|
topic_num = 5 # 51+字符: 5个关键词 (其余长文本)
|
||||||
|
|
||||||
|
topics_response, (reasoning_content, model_name) = await self.model_summary.generate_response_async(
|
||||||
|
self.find_topic_llm(text, topic_num)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 提取关键词
|
||||||
|
keywords = re.findall(r"<([^>]+)>", topics_response)
|
||||||
|
if not keywords:
|
||||||
|
keywords = []
|
||||||
|
else:
|
||||||
|
keywords = [
|
||||||
|
keyword.strip()
|
||||||
|
for keyword in ",".join(keywords).replace(",", ",").replace("、", ",").replace(" ", ",").split(",")
|
||||||
|
if keyword.strip()
|
||||||
|
]
|
||||||
|
|
||||||
|
if keywords:
|
||||||
|
logger.info(f"提取关键词: {keywords}")
|
||||||
|
|
||||||
|
return keywords
|
||||||
|
|
||||||
|
|
||||||
async def get_memory_from_text(
|
async def get_memory_from_text(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: str,
|
||||||
@@ -325,39 +385,7 @@ class Hippocampus:
|
|||||||
- memory_items: list, 该主题下的记忆项列表
|
- memory_items: list, 该主题下的记忆项列表
|
||||||
- similarity: float, 与文本的相似度
|
- similarity: float, 与文本的相似度
|
||||||
"""
|
"""
|
||||||
if not text:
|
keywords = await self.get_keywords_from_text(text)
|
||||||
return []
|
|
||||||
|
|
||||||
if fast_retrieval:
|
|
||||||
# 使用jieba分词提取关键词
|
|
||||||
words = jieba.cut(text)
|
|
||||||
# 过滤掉停用词和单字词
|
|
||||||
keywords = [word for word in words if len(word) > 1]
|
|
||||||
# 去重
|
|
||||||
keywords = list(set(keywords))
|
|
||||||
# 限制关键词数量
|
|
||||||
logger.debug(f"提取关键词: {keywords}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# 使用LLM提取关键词
|
|
||||||
topic_num = min(5, max(1, int(len(text) * 0.1))) # 根据文本长度动态调整关键词数量
|
|
||||||
# logger.info(f"提取关键词数量: {topic_num}")
|
|
||||||
topics_response, (reasoning_content, model_name) = await self.model_summary.generate_response_async(
|
|
||||||
self.find_topic_llm(text, topic_num)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 提取关键词
|
|
||||||
keywords = re.findall(r"<([^>]+)>", topics_response)
|
|
||||||
if not keywords:
|
|
||||||
keywords = []
|
|
||||||
else:
|
|
||||||
keywords = [
|
|
||||||
keyword.strip()
|
|
||||||
for keyword in ",".join(keywords).replace(",", ",").replace("、", ",").replace(" ", ",").split(",")
|
|
||||||
if keyword.strip()
|
|
||||||
]
|
|
||||||
|
|
||||||
# logger.info(f"提取的关键词: {', '.join(keywords)}")
|
|
||||||
|
|
||||||
# 过滤掉不存在于记忆图中的关键词
|
# 过滤掉不存在于记忆图中的关键词
|
||||||
valid_keywords = [keyword for keyword in keywords if keyword in self.memory_graph.G]
|
valid_keywords = [keyword for keyword in keywords if keyword in self.memory_graph.G]
|
||||||
@@ -679,38 +707,7 @@ class Hippocampus:
|
|||||||
Returns:
|
Returns:
|
||||||
float: 激活节点数与总节点数的比值
|
float: 激活节点数与总节点数的比值
|
||||||
"""
|
"""
|
||||||
if not text:
|
keywords = await self.get_keywords_from_text(text)
|
||||||
return 0
|
|
||||||
|
|
||||||
if fast_retrieval:
|
|
||||||
# 使用jieba分词提取关键词
|
|
||||||
words = jieba.cut(text)
|
|
||||||
# 过滤掉停用词和单字词
|
|
||||||
keywords = [word for word in words if len(word) > 1]
|
|
||||||
# 去重
|
|
||||||
keywords = list(set(keywords))
|
|
||||||
# 限制关键词数量
|
|
||||||
keywords = keywords[:5]
|
|
||||||
else:
|
|
||||||
# 使用LLM提取关键词
|
|
||||||
topic_num = min(5, max(1, int(len(text) * 0.1))) # 根据文本长度动态调整关键词数量
|
|
||||||
# logger.info(f"提取关键词数量: {topic_num}")
|
|
||||||
topics_response, (reasoning_content, model_name) = await self.model_summary.generate_response_async(
|
|
||||||
self.find_topic_llm(text, topic_num)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 提取关键词
|
|
||||||
keywords = re.findall(r"<([^>]+)>", topics_response)
|
|
||||||
if not keywords:
|
|
||||||
keywords = []
|
|
||||||
else:
|
|
||||||
keywords = [
|
|
||||||
keyword.strip()
|
|
||||||
for keyword in ",".join(keywords).replace(",", ",").replace("、", ",").replace(" ", ",").split(",")
|
|
||||||
if keyword.strip()
|
|
||||||
]
|
|
||||||
|
|
||||||
# logger.info(f"提取的关键词: {', '.join(keywords)}")
|
|
||||||
|
|
||||||
# 过滤掉不存在于记忆图中的关键词
|
# 过滤掉不存在于记忆图中的关键词
|
||||||
valid_keywords = [keyword for keyword in keywords if keyword in self.memory_graph.G]
|
valid_keywords = [keyword for keyword in keywords if keyword in self.memory_graph.G]
|
||||||
@@ -727,7 +724,7 @@ class Hippocampus:
|
|||||||
for keyword in valid_keywords:
|
for keyword in valid_keywords:
|
||||||
logger.debug(f"开始以关键词 '{keyword}' 为中心进行扩散检索 (最大深度: {max_depth}):")
|
logger.debug(f"开始以关键词 '{keyword}' 为中心进行扩散检索 (最大深度: {max_depth}):")
|
||||||
# 初始化激活值
|
# 初始化激活值
|
||||||
activation_values = {keyword: 1.0}
|
activation_values = {keyword: 1.5}
|
||||||
# 记录已访问的节点
|
# 记录已访问的节点
|
||||||
visited_nodes = {keyword}
|
visited_nodes = {keyword}
|
||||||
# 待处理的节点队列,每个元素是(节点, 激活值, 当前深度)
|
# 待处理的节点队列,每个元素是(节点, 激活值, 当前深度)
|
||||||
@@ -1315,6 +1312,7 @@ class ParahippocampalGyrus:
|
|||||||
return compressed_memory, similar_topics_dict
|
return compressed_memory, similar_topics_dict
|
||||||
|
|
||||||
async def operation_build_memory(self):
|
async def operation_build_memory(self):
|
||||||
|
# sourcery skip: merge-list-appends-into-extend
|
||||||
logger.info("------------------------------------开始构建记忆--------------------------------------")
|
logger.info("------------------------------------开始构建记忆--------------------------------------")
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
memory_samples = self.hippocampus.entorhinal_cortex.get_memory_sample()
|
memory_samples = self.hippocampus.entorhinal_cortex.get_memory_sample()
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ class MessageSending(MessageProcessBase):
|
|||||||
is_emoji: bool = False,
|
is_emoji: bool = False,
|
||||||
thinking_start_time: float = 0,
|
thinking_start_time: float = 0,
|
||||||
apply_set_reply_logic: bool = False,
|
apply_set_reply_logic: bool = False,
|
||||||
reply_to: str = None, # type: ignore
|
reply_to: Optional[str] = None,
|
||||||
):
|
):
|
||||||
# 调用父类初始化
|
# 调用父类初始化
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from src.plugin_system.base.base_action import BaseAction
|
|||||||
from src.chat.message_receive.chat_stream import ChatStream
|
from src.chat.message_receive.chat_stream import ChatStream
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
from src.plugin_system.base.component_types import ComponentType, ActionActivationType, ChatMode, ActionInfo
|
from src.plugin_system.base.component_types import ComponentType, ActionInfo
|
||||||
|
|
||||||
logger = get_logger("action_manager")
|
logger = get_logger("action_manager")
|
||||||
|
|
||||||
@@ -15,11 +15,6 @@ class ActionManager:
|
|||||||
现在统一使用新插件系统,简化了原有的新旧兼容逻辑。
|
现在统一使用新插件系统,简化了原有的新旧兼容逻辑。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 类常量
|
|
||||||
DEFAULT_RANDOM_PROBABILITY = 0.3
|
|
||||||
DEFAULT_MODE = ChatMode.ALL
|
|
||||||
DEFAULT_ACTIVATION_TYPE = ActionActivationType.ALWAYS
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""初始化动作管理器"""
|
"""初始化动作管理器"""
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class ActionModifier:
|
|||||||
continue # 总是激活,无需处理
|
continue # 总是激活,无需处理
|
||||||
|
|
||||||
elif activation_type == ActionActivationType.RANDOM:
|
elif activation_type == ActionActivationType.RANDOM:
|
||||||
probability = action_info.random_activation_probability or ActionManager.DEFAULT_RANDOM_PROBABILITY
|
probability = action_info.random_activation_probability
|
||||||
if random.random() >= probability:
|
if random.random() >= probability:
|
||||||
reason = f"RANDOM类型未触发(概率{probability})"
|
reason = f"RANDOM类型未触发(概率{probability})"
|
||||||
deactivated_actions.append((action_name, reason))
|
deactivated_actions.append((action_name, reason))
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ def init_prompt():
|
|||||||
{time_block}
|
{time_block}
|
||||||
{identity_block}
|
{identity_block}
|
||||||
你现在需要根据聊天内容,选择的合适的action来参与聊天。
|
你现在需要根据聊天内容,选择的合适的action来参与聊天。
|
||||||
{chat_context_description},以下是具体的聊天内容:
|
{chat_context_description},以下是具体的聊天内容
|
||||||
{chat_content_block}
|
{chat_content_block}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{moderation_prompt}
|
{moderation_prompt}
|
||||||
|
|
||||||
现在请你根据{by_what}选择合适的action和触发action的消息:
|
现在请你根据{by_what}选择合适的action和触发action的消息:
|
||||||
@@ -45,7 +46,7 @@ def init_prompt():
|
|||||||
{no_action_block}
|
{no_action_block}
|
||||||
{action_options_text}
|
{action_options_text}
|
||||||
|
|
||||||
你必须从上面列出的可用action中选择一个,并说明触发action的消息id和原因。
|
你必须从上面列出的可用action中选择一个,并说明触发action的消息id(不是消息原文)和选择该action的原因。
|
||||||
|
|
||||||
请根据动作示例,以严格的 JSON 格式输出,且仅包含 JSON 内容:
|
请根据动作示例,以严格的 JSON 格式输出,且仅包含 JSON 内容:
|
||||||
""",
|
""",
|
||||||
@@ -128,20 +129,6 @@ class ActionPlanner:
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到")
|
logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到")
|
||||||
|
|
||||||
# 如果没有可用动作或只有no_reply动作,直接返回no_reply
|
|
||||||
# 因为现在reply是永远激活,所以不需要空跳判定
|
|
||||||
# if not current_available_actions:
|
|
||||||
# action = "no_reply" if mode == ChatMode.FOCUS else "no_action"
|
|
||||||
# reasoning = "没有可用的动作"
|
|
||||||
# logger.info(f"{self.log_prefix}{reasoning}")
|
|
||||||
# return {
|
|
||||||
# "action_result": {
|
|
||||||
# "action_type": action,
|
|
||||||
# "action_data": action_data,
|
|
||||||
# "reasoning": reasoning,
|
|
||||||
# },
|
|
||||||
# }, None
|
|
||||||
|
|
||||||
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
|
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
|
||||||
prompt, message_id_list = await self.build_planner_prompt(
|
prompt, message_id_list = await self.build_planner_prompt(
|
||||||
is_group_chat=is_group_chat, # <-- Pass HFC state
|
is_group_chat=is_group_chat, # <-- Pass HFC state
|
||||||
@@ -224,7 +211,7 @@ class ActionPlanner:
|
|||||||
reasoning = f"Planner 内部处理错误: {outer_e}"
|
reasoning = f"Planner 内部处理错误: {outer_e}"
|
||||||
|
|
||||||
is_parallel = False
|
is_parallel = False
|
||||||
if action in current_available_actions:
|
if mode == ChatMode.NORMAL and action in current_available_actions:
|
||||||
is_parallel = current_available_actions[action].parallel_action
|
is_parallel = current_available_actions[action].parallel_action
|
||||||
|
|
||||||
action_result = {
|
action_result = {
|
||||||
@@ -268,7 +255,7 @@ class ActionPlanner:
|
|||||||
|
|
||||||
actions_before_now = get_actions_by_timestamp_with_chat(
|
actions_before_now = get_actions_by_timestamp_with_chat(
|
||||||
chat_id=self.chat_id,
|
chat_id=self.chat_id,
|
||||||
timestamp_start=time.time()-3600,
|
timestamp_start=time.time() - 3600,
|
||||||
timestamp_end=time.time(),
|
timestamp_end=time.time(),
|
||||||
limit=5,
|
limit=5,
|
||||||
)
|
)
|
||||||
@@ -276,7 +263,7 @@ class ActionPlanner:
|
|||||||
actions_before_now_block = build_readable_actions(
|
actions_before_now_block = build_readable_actions(
|
||||||
actions=actions_before_now,
|
actions=actions_before_now,
|
||||||
)
|
)
|
||||||
|
|
||||||
actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}"
|
actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}"
|
||||||
|
|
||||||
self.last_obs_time_mark = time.time()
|
self.last_obs_time_mark = time.time()
|
||||||
@@ -288,7 +275,6 @@ class ActionPlanner:
|
|||||||
if global_config.chat.at_bot_inevitable_reply:
|
if global_config.chat.at_bot_inevitable_reply:
|
||||||
mentioned_bonus = "\n- 有人提到你,或者at你"
|
mentioned_bonus = "\n- 有人提到你,或者at你"
|
||||||
|
|
||||||
|
|
||||||
by_what = "聊天内容"
|
by_what = "聊天内容"
|
||||||
target_prompt = '\n "target_message_id":"触发action的消息id"'
|
target_prompt = '\n "target_message_id":"触发action的消息id"'
|
||||||
no_action_block = f"""重要说明:
|
no_action_block = f"""重要说明:
|
||||||
@@ -311,7 +297,7 @@ class ActionPlanner:
|
|||||||
by_what = "聊天内容和用户的最新消息"
|
by_what = "聊天内容和用户的最新消息"
|
||||||
target_prompt = ""
|
target_prompt = ""
|
||||||
no_action_block = """重要说明:
|
no_action_block = """重要说明:
|
||||||
- 'no_action' 表示只进行普通聊天回复,不执行任何额外动作
|
- 'reply' 表示只进行普通聊天回复,不执行任何额外动作
|
||||||
- 其他action表示在普通回复的基础上,执行相应的额外动作"""
|
- 其他action表示在普通回复的基础上,执行相应的额外动作"""
|
||||||
|
|
||||||
chat_context_description = "你现在正在一个群聊中"
|
chat_context_description = "你现在正在一个群聊中"
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ from src.chat.message_receive.uni_message_sender import HeartFCSender
|
|||||||
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
|
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
|
||||||
from src.chat.utils.utils import get_chat_type_and_target_info
|
from src.chat.utils.utils import get_chat_type_and_target_info
|
||||||
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
|
||||||
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
|
from src.chat.utils.chat_message_builder import (
|
||||||
|
build_readable_messages,
|
||||||
|
get_raw_msg_before_timestamp_with_chat,
|
||||||
|
replace_user_references_sync,
|
||||||
|
)
|
||||||
from src.chat.express.expression_selector import expression_selector
|
from src.chat.express.expression_selector import expression_selector
|
||||||
from src.chat.knowledge.knowledge_lib import qa_manager
|
from src.chat.knowledge.knowledge_lib import qa_manager
|
||||||
from src.chat.memory_system.memory_activator import MemoryActivator
|
from src.chat.memory_system.memory_activator import MemoryActivator
|
||||||
@@ -25,42 +29,16 @@ from src.chat.memory_system.instant_memory import InstantMemory
|
|||||||
from src.mood.mood_manager import mood_manager
|
from src.mood.mood_manager import mood_manager
|
||||||
from src.person_info.relationship_fetcher import relationship_fetcher_manager
|
from src.person_info.relationship_fetcher import relationship_fetcher_manager
|
||||||
from src.person_info.person_info import get_person_info_manager
|
from src.person_info.person_info import get_person_info_manager
|
||||||
from src.tools.tool_executor import ToolExecutor
|
|
||||||
from src.plugin_system.base.component_types import ActionInfo
|
from src.plugin_system.base.component_types import ActionInfo
|
||||||
|
|
||||||
logger = get_logger("replyer")
|
logger = get_logger("replyer")
|
||||||
|
|
||||||
|
|
||||||
def init_prompt():
|
def init_prompt():
|
||||||
Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1")
|
Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1")
|
||||||
Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1")
|
Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1")
|
||||||
Prompt("在群里聊天", "chat_target_group2")
|
Prompt("在群里聊天", "chat_target_group2")
|
||||||
Prompt("和{sender_name}聊天", "chat_target_private2")
|
Prompt("和{sender_name}聊天", "chat_target_private2")
|
||||||
Prompt("\n你有以下这些**知识**:\n{prompt_info}\n请你**记住上面的知识**,之后可能会用到。\n", "knowledge_prompt")
|
|
||||||
|
|
||||||
Prompt(
|
|
||||||
"""
|
|
||||||
{expression_habits_block}
|
|
||||||
{tool_info_block}
|
|
||||||
{knowledge_prompt}
|
|
||||||
{memory_block}
|
|
||||||
{relation_info_block}
|
|
||||||
{extra_info_block}
|
|
||||||
|
|
||||||
{chat_target}
|
|
||||||
{time_block}
|
|
||||||
{chat_info}
|
|
||||||
{reply_target_block}
|
|
||||||
{identity}
|
|
||||||
|
|
||||||
{action_descriptions}
|
|
||||||
你正在{chat_target_2},你现在的心情是:{mood_state}
|
|
||||||
现在请你读读之前的聊天记录,并给出回复
|
|
||||||
{config_expression_style}。注意不要复读你说过的话
|
|
||||||
{keywords_reaction_prompt}
|
|
||||||
{moderation_prompt}
|
|
||||||
不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出回复内容""",
|
|
||||||
"default_generator_prompt",
|
|
||||||
)
|
|
||||||
|
|
||||||
Prompt(
|
Prompt(
|
||||||
"""
|
"""
|
||||||
@@ -109,7 +87,8 @@ def init_prompt():
|
|||||||
{core_dialogue_prompt}
|
{core_dialogue_prompt}
|
||||||
|
|
||||||
{reply_target_block}
|
{reply_target_block}
|
||||||
对方最新发送的内容:{message_txt}
|
|
||||||
|
|
||||||
你现在的心情是:{mood_state}
|
你现在的心情是:{mood_state}
|
||||||
{config_expression_style}
|
{config_expression_style}
|
||||||
注意不要复读你说过的话
|
注意不要复读你说过的话
|
||||||
@@ -159,6 +138,8 @@ class DefaultReplyer:
|
|||||||
self.heart_fc_sender = HeartFCSender()
|
self.heart_fc_sender = HeartFCSender()
|
||||||
self.memory_activator = MemoryActivator()
|
self.memory_activator = MemoryActivator()
|
||||||
self.instant_memory = InstantMemory(chat_id=self.chat_stream.stream_id)
|
self.instant_memory = InstantMemory(chat_id=self.chat_stream.stream_id)
|
||||||
|
|
||||||
|
from src.plugin_system.core.tool_use import ToolExecutor # 延迟导入ToolExecutor,不然会循环依赖
|
||||||
self.tool_executor = ToolExecutor(chat_id=self.chat_stream.stream_id, enable_cache=True, cache_ttl=3)
|
self.tool_executor = ToolExecutor(chat_id=self.chat_stream.stream_id, enable_cache=True, cache_ttl=3)
|
||||||
|
|
||||||
def _select_weighted_model_config(self) -> Dict[str, Any]:
|
def _select_weighted_model_config(self) -> Dict[str, Any]:
|
||||||
@@ -171,67 +152,49 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
async def generate_reply_with_context(
|
async def generate_reply_with_context(
|
||||||
self,
|
self,
|
||||||
reply_data: Optional[Dict[str, Any]] = None,
|
|
||||||
reply_to: str = "",
|
reply_to: str = "",
|
||||||
extra_info: str = "",
|
extra_info: str = "",
|
||||||
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||||||
enable_tool: bool = True,
|
enable_tool: bool = True,
|
||||||
enable_timeout: bool = False,
|
|
||||||
) -> Tuple[bool, Optional[str], Optional[str]]:
|
) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||||
"""
|
"""
|
||||||
回复器 (Replier): 核心逻辑,负责生成回复文本。
|
回复器 (Replier): 负责生成回复文本的核心逻辑。
|
||||||
(已整合原 HeartFCGenerator 的功能)
|
|
||||||
|
Args:
|
||||||
|
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||||||
|
extra_info: 额外信息,用于补充上下文
|
||||||
|
available_actions: 可用的动作信息字典
|
||||||
|
enable_tool: 是否启用工具调用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[bool, Optional[str], Optional[str]]: (是否成功, 生成的回复内容, 使用的prompt)
|
||||||
"""
|
"""
|
||||||
prompt = None
|
prompt = None
|
||||||
if available_actions is None:
|
if available_actions is None:
|
||||||
available_actions = {}
|
available_actions = {}
|
||||||
try:
|
try:
|
||||||
if not reply_data:
|
|
||||||
reply_data = {
|
|
||||||
"reply_to": reply_to,
|
|
||||||
"extra_info": extra_info,
|
|
||||||
}
|
|
||||||
for key, value in reply_data.items():
|
|
||||||
if not value:
|
|
||||||
logger.debug(f"回复数据跳过{key},生成回复时将忽略。")
|
|
||||||
|
|
||||||
# 3. 构建 Prompt
|
# 3. 构建 Prompt
|
||||||
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
||||||
prompt = await self.build_prompt_reply_context(
|
prompt = await self.build_prompt_reply_context(
|
||||||
reply_data=reply_data, # 传递action_data
|
reply_to=reply_to,
|
||||||
|
extra_info=extra_info,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
enable_timeout=enable_timeout,
|
|
||||||
enable_tool=enable_tool,
|
enable_tool=enable_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not prompt:
|
||||||
|
logger.warning("构建prompt失败,跳过回复生成")
|
||||||
|
return False, None, None
|
||||||
|
|
||||||
# 4. 调用 LLM 生成回复
|
# 4. 调用 LLM 生成回复
|
||||||
content = None
|
content = None
|
||||||
reasoning_content = None
|
# TODO: 复活这里
|
||||||
model_name = "unknown_model"
|
# reasoning_content = None
|
||||||
|
# model_name = "unknown_model"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with Timer("LLM生成", {}): # 内部计时器,可选保留
|
content = await self.llm_generate_content(prompt)
|
||||||
# 加权随机选择一个模型配置
|
logger.debug(f"replyer生成内容: {content}")
|
||||||
selected_model_config = self._select_weighted_model_config()
|
|
||||||
# 兼容新旧格式的模型名称获取
|
|
||||||
model_display_name = selected_model_config.get('model_name', selected_model_config.get('name', 'N/A'))
|
|
||||||
logger.info(
|
|
||||||
f"使用模型生成回复: {model_display_name} (选中概率: {selected_model_config.get('weight', 1.0)})"
|
|
||||||
)
|
|
||||||
|
|
||||||
express_model = LLMRequest(
|
|
||||||
model=selected_model_config,
|
|
||||||
request_type=self.request_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
if global_config.debug.show_prompt:
|
|
||||||
logger.info(f"\n{prompt}\n")
|
|
||||||
else:
|
|
||||||
logger.debug(f"\n{prompt}\n")
|
|
||||||
|
|
||||||
content, (reasoning_content, model_name) = await express_model.generate_response_async(prompt)
|
|
||||||
|
|
||||||
logger.debug(f"replyer生成内容: {content}")
|
|
||||||
|
|
||||||
except Exception as llm_e:
|
except Exception as llm_e:
|
||||||
# 精简报错信息
|
# 精简报错信息
|
||||||
@@ -247,73 +210,62 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
async def rewrite_reply_with_context(
|
async def rewrite_reply_with_context(
|
||||||
self,
|
self,
|
||||||
reply_data: Dict[str, Any],
|
|
||||||
raw_reply: str = "",
|
raw_reply: str = "",
|
||||||
reason: str = "",
|
reason: str = "",
|
||||||
reply_to: str = "",
|
reply_to: str = "",
|
||||||
relation_info: str = "",
|
return_prompt: bool = False,
|
||||||
) -> Tuple[bool, Optional[str]]:
|
) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||||
"""
|
"""
|
||||||
表达器 (Expressor): 核心逻辑,负责生成回复文本。
|
表达器 (Expressor): 负责重写和优化回复文本。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
raw_reply: 原始回复内容
|
||||||
|
reason: 回复原因
|
||||||
|
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||||||
|
relation_info: 关系信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[bool, Optional[str]]: (是否成功, 重写后的回复内容)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if not reply_data:
|
|
||||||
reply_data = {
|
|
||||||
"reply_to": reply_to,
|
|
||||||
"relation_info": relation_info,
|
|
||||||
}
|
|
||||||
|
|
||||||
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
||||||
prompt = await self.build_prompt_rewrite_context(
|
prompt = await self.build_prompt_rewrite_context(
|
||||||
reply_data=reply_data,
|
raw_reply=raw_reply,
|
||||||
|
reason=reason,
|
||||||
|
reply_to=reply_to,
|
||||||
)
|
)
|
||||||
|
|
||||||
content = None
|
content = None
|
||||||
reasoning_content = None
|
# TODO: 复活这里
|
||||||
model_name = "unknown_model"
|
# reasoning_content = None
|
||||||
|
# model_name = "unknown_model"
|
||||||
if not prompt:
|
if not prompt:
|
||||||
logger.error("Prompt 构建失败,无法生成回复。")
|
logger.error("Prompt 构建失败,无法生成回复。")
|
||||||
return False, None
|
return False, None, None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with Timer("LLM生成", {}): # 内部计时器,可选保留
|
content = await self.llm_generate_content(prompt)
|
||||||
# 加权随机选择一个模型配置
|
logger.info(f"想要表达:{raw_reply}||理由:{reason}||生成回复: {content}\n")
|
||||||
selected_model_config = self._select_weighted_model_config()
|
|
||||||
# 兼容新旧格式的模型名称获取
|
|
||||||
model_display_name = selected_model_config.get('model_name', selected_model_config.get('name', 'N/A'))
|
|
||||||
logger.info(
|
|
||||||
f"使用模型重写回复: {model_display_name} (选中概率: {selected_model_config.get('weight', 1.0)})"
|
|
||||||
)
|
|
||||||
|
|
||||||
express_model = LLMRequest(
|
|
||||||
model=selected_model_config,
|
|
||||||
request_type=self.request_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
content, (reasoning_content, model_name) = await express_model.generate_response_async(prompt)
|
|
||||||
|
|
||||||
logger.info(f"想要表达:{raw_reply}||理由:{reason}||生成回复: {content}\n")
|
|
||||||
|
|
||||||
except Exception as llm_e:
|
except Exception as llm_e:
|
||||||
# 精简报错信息
|
# 精简报错信息
|
||||||
logger.error(f"LLM 生成失败: {llm_e}")
|
logger.error(f"LLM 生成失败: {llm_e}")
|
||||||
return False, None # LLM 调用失败则无法生成回复
|
return False, None, prompt if return_prompt else None # LLM 调用失败则无法生成回复
|
||||||
|
|
||||||
return True, content
|
return True, content, prompt if return_prompt else None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"回复生成意外失败: {e}")
|
logger.error(f"回复生成意外失败: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False, None
|
return False, None, prompt if return_prompt else None
|
||||||
|
|
||||||
async def build_relation_info(self, reply_data=None):
|
async def build_relation_info(self, reply_to: str = ""):
|
||||||
if not global_config.relationship.enable_relationship:
|
if not global_config.relationship.enable_relationship:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id)
|
relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id)
|
||||||
if not reply_data:
|
if not reply_to:
|
||||||
return ""
|
return ""
|
||||||
reply_to = reply_data.get("reply_to", "")
|
|
||||||
sender, text = self._parse_reply_target(reply_to)
|
sender, text = self._parse_reply_target(reply_to)
|
||||||
if not sender or not text:
|
if not sender or not text:
|
||||||
return ""
|
return ""
|
||||||
@@ -327,7 +279,16 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
return await relationship_fetcher.build_relation_info(person_id, points_num=5)
|
return await relationship_fetcher.build_relation_info(person_id, points_num=5)
|
||||||
|
|
||||||
async def build_expression_habits(self, chat_history, target):
|
async def build_expression_habits(self, chat_history: str, target: str) -> str:
|
||||||
|
"""构建表达习惯块
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_history: 聊天历史记录
|
||||||
|
target: 目标消息内容
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 表达习惯信息字符串
|
||||||
|
"""
|
||||||
if not global_config.expression.enable_expression:
|
if not global_config.expression.enable_expression:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -360,54 +321,65 @@ class DefaultReplyer:
|
|||||||
expression_habits_block = ""
|
expression_habits_block = ""
|
||||||
expression_habits_title = ""
|
expression_habits_title = ""
|
||||||
if style_habits_str.strip():
|
if style_habits_str.strip():
|
||||||
expression_habits_title = "你可以参考以下的语言习惯,当情景合适就使用,但不要生硬使用,以合理的方式结合到你的回复中:"
|
expression_habits_title = (
|
||||||
|
"你可以参考以下的语言习惯,当情景合适就使用,但不要生硬使用,以合理的方式结合到你的回复中:"
|
||||||
|
)
|
||||||
expression_habits_block += f"{style_habits_str}\n"
|
expression_habits_block += f"{style_habits_str}\n"
|
||||||
if grammar_habits_str.strip():
|
if grammar_habits_str.strip():
|
||||||
expression_habits_title = "你可以选择下面的句法进行回复,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式使用:"
|
expression_habits_title = (
|
||||||
|
"你可以选择下面的句法进行回复,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式使用:"
|
||||||
|
)
|
||||||
expression_habits_block += f"{grammar_habits_str}\n"
|
expression_habits_block += f"{grammar_habits_str}\n"
|
||||||
|
|
||||||
if style_habits_str.strip() and grammar_habits_str.strip():
|
if style_habits_str.strip() and grammar_habits_str.strip():
|
||||||
expression_habits_title = "你可以参考以下的语言习惯和句法,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式结合到你的回复中:"
|
expression_habits_title = "你可以参考以下的语言习惯和句法,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式结合到你的回复中:"
|
||||||
|
|
||||||
expression_habits_block = f"{expression_habits_title}\n{expression_habits_block}"
|
|
||||||
|
|
||||||
|
|
||||||
return expression_habits_block
|
return f"{expression_habits_title}\n{expression_habits_block}"
|
||||||
|
|
||||||
async def build_memory_block(self, chat_history, target):
|
async def build_memory_block(self, chat_history: str, target: str) -> str:
|
||||||
|
"""构建记忆块
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_history: 聊天历史记录
|
||||||
|
target: 目标消息内容
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 记忆信息字符串
|
||||||
|
"""
|
||||||
if not global_config.memory.enable_memory:
|
if not global_config.memory.enable_memory:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
instant_memory = None
|
instant_memory = None
|
||||||
|
|
||||||
running_memories = await self.memory_activator.activate_memory_with_chat_history(
|
running_memories = await self.memory_activator.activate_memory_with_chat_history(
|
||||||
target_message=target, chat_history_prompt=chat_history
|
target_message=target, chat_history_prompt=chat_history
|
||||||
)
|
)
|
||||||
|
|
||||||
if global_config.memory.enable_instant_memory:
|
if global_config.memory.enable_instant_memory:
|
||||||
asyncio.create_task(self.instant_memory.create_and_store_memory(chat_history))
|
asyncio.create_task(self.instant_memory.create_and_store_memory(chat_history))
|
||||||
|
|
||||||
instant_memory = await self.instant_memory.get_memory(target)
|
instant_memory = await self.instant_memory.get_memory(target)
|
||||||
logger.info(f"即时记忆:{instant_memory}")
|
logger.info(f"即时记忆:{instant_memory}")
|
||||||
|
|
||||||
if not running_memories:
|
if not running_memories:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
|
memory_str = "以下是当前在聊天中,你回忆起的记忆:\n"
|
||||||
for running_memory in running_memories:
|
for running_memory in running_memories:
|
||||||
memory_str += f"- {running_memory['content']}\n"
|
memory_str += f"- {running_memory['content']}\n"
|
||||||
|
|
||||||
if instant_memory:
|
if instant_memory:
|
||||||
memory_str += f"- {instant_memory}\n"
|
memory_str += f"- {instant_memory}\n"
|
||||||
|
|
||||||
return memory_str
|
return memory_str
|
||||||
|
|
||||||
async def build_tool_info(self, chat_history, reply_data: Optional[Dict], enable_tool: bool = True):
|
async def build_tool_info(self, chat_history: str, reply_to: str = "", enable_tool: bool = True) -> str:
|
||||||
"""构建工具信息块
|
"""构建工具信息块
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reply_data: 回复数据,包含要回复的消息内容
|
chat_history: 聊天历史记录
|
||||||
chat_history: 聊天历史
|
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||||||
|
enable_tool: 是否启用工具调用
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 工具信息字符串
|
str: 工具信息字符串
|
||||||
@@ -416,10 +388,9 @@ class DefaultReplyer:
|
|||||||
if not enable_tool:
|
if not enable_tool:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if not reply_data:
|
if not reply_to:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
reply_to = reply_data.get("reply_to", "")
|
|
||||||
sender, text = self._parse_reply_target(reply_to)
|
sender, text = self._parse_reply_target(reply_to)
|
||||||
|
|
||||||
if not text:
|
if not text:
|
||||||
@@ -442,7 +413,7 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
tool_info_str += "以上是你获取到的实时信息,请在回复时参考这些信息。"
|
tool_info_str += "以上是你获取到的实时信息,请在回复时参考这些信息。"
|
||||||
logger.info(f"获取到 {len(tool_results)} 个工具结果")
|
logger.info(f"获取到 {len(tool_results)} 个工具结果")
|
||||||
|
|
||||||
return tool_info_str
|
return tool_info_str
|
||||||
else:
|
else:
|
||||||
logger.debug("未获取到任何工具结果")
|
logger.debug("未获取到任何工具结果")
|
||||||
@@ -452,7 +423,15 @@ class DefaultReplyer:
|
|||||||
logger.error(f"工具信息获取失败: {e}")
|
logger.error(f"工具信息获取失败: {e}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _parse_reply_target(self, target_message: str) -> tuple:
|
def _parse_reply_target(self, target_message: str) -> Tuple[str, str]:
|
||||||
|
"""解析回复目标消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_message: 目标消息,格式为 "发送者:消息内容" 或 "发送者:消息内容"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[str, str]: (发送者名称, 消息内容)
|
||||||
|
"""
|
||||||
sender = ""
|
sender = ""
|
||||||
target = ""
|
target = ""
|
||||||
# 添加None检查,防止NoneType错误
|
# 添加None检查,防止NoneType错误
|
||||||
@@ -466,14 +445,22 @@ class DefaultReplyer:
|
|||||||
target = parts[1].strip()
|
target = parts[1].strip()
|
||||||
return sender, target
|
return sender, target
|
||||||
|
|
||||||
async def build_keywords_reaction_prompt(self, target):
|
async def build_keywords_reaction_prompt(self, target: Optional[str]) -> str:
|
||||||
|
"""构建关键词反应提示
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target: 目标消息内容
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 关键词反应提示字符串
|
||||||
|
"""
|
||||||
# 关键词检测与反应
|
# 关键词检测与反应
|
||||||
keywords_reaction_prompt = ""
|
keywords_reaction_prompt = ""
|
||||||
try:
|
try:
|
||||||
# 添加None检查,防止NoneType错误
|
# 添加None检查,防止NoneType错误
|
||||||
if target is None:
|
if target is None:
|
||||||
return keywords_reaction_prompt
|
return keywords_reaction_prompt
|
||||||
|
|
||||||
# 处理关键词规则
|
# 处理关键词规则
|
||||||
for rule in global_config.keyword_reaction.keyword_rules:
|
for rule in global_config.keyword_reaction.keyword_rules:
|
||||||
if any(keyword in target for keyword in rule.keywords):
|
if any(keyword in target for keyword in rule.keywords):
|
||||||
@@ -500,15 +487,25 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
return keywords_reaction_prompt
|
return keywords_reaction_prompt
|
||||||
|
|
||||||
async def _time_and_run_task(self, coroutine, name: str):
|
async def _time_and_run_task(self, coroutine, name: str) -> Tuple[str, Any, float]:
|
||||||
"""一个简单的帮助函数,用于计时和运行异步任务,返回任务名、结果和耗时"""
|
"""计时并运行异步任务的辅助函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
coroutine: 要执行的协程
|
||||||
|
name: 任务名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[str, Any, float]: (任务名称, 任务结果, 执行耗时)
|
||||||
|
"""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
result = await coroutine
|
result = await coroutine
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
duration = end_time - start_time
|
duration = end_time - start_time
|
||||||
return name, result, duration
|
return name, result, duration
|
||||||
|
|
||||||
def build_s4u_chat_history_prompts(self, message_list_before_now: list, target_user_id: str) -> tuple[str, str]:
|
def build_s4u_chat_history_prompts(
|
||||||
|
self, message_list_before_now: List[Dict[str, Any]], target_user_id: str
|
||||||
|
) -> Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
构建 s4u 风格的分离对话 prompt
|
构建 s4u 风格的分离对话 prompt
|
||||||
|
|
||||||
@@ -517,7 +514,7 @@ class DefaultReplyer:
|
|||||||
target_user_id: 目标用户ID(当前对话对象)
|
target_user_id: 目标用户ID(当前对话对象)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (核心对话prompt, 背景对话prompt)
|
Tuple[str, str]: (核心对话prompt, 背景对话prompt)
|
||||||
"""
|
"""
|
||||||
core_dialogue_list = []
|
core_dialogue_list = []
|
||||||
background_dialogue_list = []
|
background_dialogue_list = []
|
||||||
@@ -536,7 +533,7 @@ class DefaultReplyer:
|
|||||||
# 其他用户的对话
|
# 其他用户的对话
|
||||||
background_dialogue_list.append(msg_dict)
|
background_dialogue_list.append(msg_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"记录: {msg_dict}, 错误: {e}")
|
logger.error(f"处理消息记录时出错: {msg_dict}, 错误: {e}")
|
||||||
|
|
||||||
# 构建背景对话 prompt
|
# 构建背景对话 prompt
|
||||||
background_dialogue_prompt = ""
|
background_dialogue_prompt = ""
|
||||||
@@ -581,8 +578,25 @@ class DefaultReplyer:
|
|||||||
sender: str,
|
sender: str,
|
||||||
target: str,
|
target: str,
|
||||||
chat_info: str,
|
chat_info: str,
|
||||||
):
|
) -> Any:
|
||||||
"""构建 mai_think 上下文信息"""
|
"""构建 mai_think 上下文信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 聊天ID
|
||||||
|
memory_block: 记忆块内容
|
||||||
|
relation_info: 关系信息
|
||||||
|
time_block: 时间块内容
|
||||||
|
chat_target_1: 聊天目标1
|
||||||
|
chat_target_2: 聊天目标2
|
||||||
|
mood_prompt: 情绪提示
|
||||||
|
identity_block: 身份块内容
|
||||||
|
sender: 发送者名称
|
||||||
|
target: 目标消息内容
|
||||||
|
chat_info: 聊天信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: mai_think 实例
|
||||||
|
"""
|
||||||
mai_think = mai_thinking_manager.get_mai_think(chat_id)
|
mai_think = mai_thinking_manager.get_mai_think(chat_id)
|
||||||
mai_think.memory_block = memory_block
|
mai_think.memory_block = memory_block
|
||||||
mai_think.relation_info_block = relation_info
|
mai_think.relation_info_block = relation_info
|
||||||
@@ -598,21 +612,20 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
async def build_prompt_reply_context(
|
async def build_prompt_reply_context(
|
||||||
self,
|
self,
|
||||||
reply_data: Dict[str, Any],
|
reply_to: str,
|
||||||
|
extra_info: str = "",
|
||||||
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||||||
enable_timeout: bool = False,
|
|
||||||
enable_tool: bool = True,
|
enable_tool: bool = True,
|
||||||
) -> str: # sourcery skip: merge-else-if-into-elif, remove-redundant-if
|
) -> str: # sourcery skip: merge-else-if-into-elif, remove-redundant-if
|
||||||
"""
|
"""
|
||||||
构建回复器上下文
|
构建回复器上下文
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reply_data: 回复数据
|
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||||||
replay_data 包含以下字段:
|
extra_info: 额外信息,用于补充上下文
|
||||||
structured_info: 结构化信息,一般是工具调用获得的信息
|
|
||||||
reply_to: 回复对象
|
|
||||||
extra_info/extra_info_block: 额外信息
|
|
||||||
available_actions: 可用动作
|
available_actions: 可用动作
|
||||||
|
enable_timeout: 是否启用超时处理
|
||||||
|
enable_tool: 是否启用工具调用
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: 构建好的上下文
|
str: 构建好的上下文
|
||||||
@@ -623,9 +636,7 @@ class DefaultReplyer:
|
|||||||
chat_id = chat_stream.stream_id
|
chat_id = chat_stream.stream_id
|
||||||
person_info_manager = get_person_info_manager()
|
person_info_manager = get_person_info_manager()
|
||||||
is_group_chat = bool(chat_stream.group_info)
|
is_group_chat = bool(chat_stream.group_info)
|
||||||
reply_to = reply_data.get("reply_to", "none")
|
|
||||||
extra_info_block = reply_data.get("extra_info", "") or reply_data.get("extra_info_block", "")
|
|
||||||
|
|
||||||
if global_config.mood.enable_mood:
|
if global_config.mood.enable_mood:
|
||||||
chat_mood = mood_manager.get_mood_by_chat_id(chat_id)
|
chat_mood = mood_manager.get_mood_by_chat_id(chat_id)
|
||||||
mood_prompt = chat_mood.mood_state
|
mood_prompt = chat_mood.mood_state
|
||||||
@@ -633,6 +644,15 @@ class DefaultReplyer:
|
|||||||
mood_prompt = ""
|
mood_prompt = ""
|
||||||
|
|
||||||
sender, target = self._parse_reply_target(reply_to)
|
sender, target = self._parse_reply_target(reply_to)
|
||||||
|
person_info_manager = get_person_info_manager()
|
||||||
|
person_id = person_info_manager.get_person_id_by_person_name(sender)
|
||||||
|
user_id = person_info_manager.get_value_sync(person_id, "user_id")
|
||||||
|
platform = chat_stream.platform
|
||||||
|
if user_id == global_config.bot.qq_account and platform == global_config.bot.platform:
|
||||||
|
logger.warning("选取了自身作为回复对象,跳过构建prompt")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
|
||||||
|
|
||||||
# 构建action描述 (如果启用planner)
|
# 构建action描述 (如果启用planner)
|
||||||
action_descriptions = ""
|
action_descriptions = ""
|
||||||
@@ -649,21 +669,6 @@ class DefaultReplyer:
|
|||||||
limit=global_config.chat.max_context_size * 2,
|
limit=global_config.chat.max_context_size * 2,
|
||||||
)
|
)
|
||||||
|
|
||||||
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
|
|
||||||
chat_id=chat_id,
|
|
||||||
timestamp=time.time(),
|
|
||||||
limit=global_config.chat.max_context_size,
|
|
||||||
)
|
|
||||||
chat_talking_prompt = build_readable_messages(
|
|
||||||
message_list_before_now,
|
|
||||||
replace_bot_name=True,
|
|
||||||
merge_messages=False,
|
|
||||||
timestamp_mode="normal_no_YMD",
|
|
||||||
read_mark=0.0,
|
|
||||||
truncate=True,
|
|
||||||
show_actions=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
message_list_before_short = get_raw_msg_before_timestamp_with_chat(
|
message_list_before_short = get_raw_msg_before_timestamp_with_chat(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
timestamp=time.time(),
|
timestamp=time.time(),
|
||||||
@@ -683,25 +688,21 @@ class DefaultReplyer:
|
|||||||
self._time_and_run_task(
|
self._time_and_run_task(
|
||||||
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
|
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
|
||||||
),
|
),
|
||||||
self._time_and_run_task(
|
self._time_and_run_task(self.build_relation_info(reply_to), "relation_info"),
|
||||||
self.build_relation_info(reply_data), "relation_info"
|
|
||||||
),
|
|
||||||
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
|
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
|
||||||
self._time_and_run_task(
|
self._time_and_run_task(
|
||||||
self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "tool_info"
|
self.build_tool_info(chat_talking_prompt_short, reply_to, enable_tool=enable_tool), "tool_info"
|
||||||
),
|
|
||||||
self._time_and_run_task(
|
|
||||||
get_prompt_info(target, threshold=0.38), "prompt_info"
|
|
||||||
),
|
),
|
||||||
|
self._time_and_run_task(get_prompt_info(target, threshold=0.38), "prompt_info"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 任务名称中英文映射
|
# 任务名称中英文映射
|
||||||
task_name_mapping = {
|
task_name_mapping = {
|
||||||
"expression_habits": "选取表达方式",
|
"expression_habits": "选取表达方式",
|
||||||
"relation_info": "感受关系",
|
"relation_info": "感受关系",
|
||||||
"memory_block": "回忆",
|
"memory_block": "回忆",
|
||||||
"tool_info": "使用工具",
|
"tool_info": "使用工具",
|
||||||
"prompt_info": "获取知识"
|
"prompt_info": "获取知识",
|
||||||
}
|
}
|
||||||
|
|
||||||
# 处理结果
|
# 处理结果
|
||||||
@@ -723,8 +724,8 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
||||||
|
|
||||||
if extra_info_block:
|
if extra_info:
|
||||||
extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info_block}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策"
|
extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策"
|
||||||
else:
|
else:
|
||||||
extra_info_block = ""
|
extra_info_block = ""
|
||||||
|
|
||||||
@@ -779,116 +780,74 @@ class DefaultReplyer:
|
|||||||
# 根据sender通过person_info_manager反向查找person_id,再获取user_id
|
# 根据sender通过person_info_manager反向查找person_id,再获取user_id
|
||||||
person_id = person_info_manager.get_person_id_by_person_name(sender)
|
person_id = person_info_manager.get_person_id_by_person_name(sender)
|
||||||
|
|
||||||
# 根据配置选择使用哪种 prompt 构建模式
|
# 使用 s4u 对话构建模式:分离当前对话对象和其他对话
|
||||||
if global_config.chat.use_s4u_prompt_mode and person_id:
|
try:
|
||||||
# 使用 s4u 对话构建模式:分离当前对话对象和其他对话
|
user_id_value = await person_info_manager.get_value(person_id, "user_id")
|
||||||
try:
|
if user_id_value:
|
||||||
user_id_value = await person_info_manager.get_value(person_id, "user_id")
|
target_user_id = str(user_id_value)
|
||||||
if user_id_value:
|
except Exception as e:
|
||||||
target_user_id = str(user_id_value)
|
logger.warning(f"无法从person_id {person_id} 获取user_id: {e}")
|
||||||
except Exception as e:
|
target_user_id = ""
|
||||||
logger.warning(f"无法从person_id {person_id} 获取user_id: {e}")
|
|
||||||
target_user_id = ""
|
|
||||||
|
|
||||||
# 构建分离的对话 prompt
|
# 构建分离的对话 prompt
|
||||||
core_dialogue_prompt, background_dialogue_prompt = self.build_s4u_chat_history_prompts(
|
core_dialogue_prompt, background_dialogue_prompt = self.build_s4u_chat_history_prompts(
|
||||||
message_list_before_now_long, target_user_id
|
message_list_before_now_long, target_user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
self.build_mai_think_context(
|
self.build_mai_think_context(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
memory_block=memory_block,
|
memory_block=memory_block,
|
||||||
relation_info=relation_info,
|
relation_info=relation_info,
|
||||||
time_block=time_block,
|
time_block=time_block,
|
||||||
chat_target_1=chat_target_1,
|
chat_target_1=chat_target_1,
|
||||||
chat_target_2=chat_target_2,
|
chat_target_2=chat_target_2,
|
||||||
mood_prompt=mood_prompt,
|
mood_prompt=mood_prompt,
|
||||||
identity_block=identity_block,
|
identity_block=identity_block,
|
||||||
sender=sender,
|
sender=sender,
|
||||||
target=target,
|
target=target,
|
||||||
chat_info=f"""
|
chat_info=f"""
|
||||||
{background_dialogue_prompt}
|
{background_dialogue_prompt}
|
||||||
--------------------------------
|
--------------------------------
|
||||||
{time_block}
|
{time_block}
|
||||||
这是你和{sender}的对话,你们正在交流中:
|
这是你和{sender}的对话,你们正在交流中:
|
||||||
{core_dialogue_prompt}"""
|
{core_dialogue_prompt}""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 使用 s4u 风格的模板
|
# 使用 s4u 风格的模板
|
||||||
template_name = "s4u_style_prompt"
|
template_name = "s4u_style_prompt"
|
||||||
|
|
||||||
return await global_prompt_manager.format_prompt(
|
return await global_prompt_manager.format_prompt(
|
||||||
template_name,
|
template_name,
|
||||||
expression_habits_block=expression_habits_block,
|
expression_habits_block=expression_habits_block,
|
||||||
tool_info_block=tool_info,
|
tool_info_block=tool_info,
|
||||||
knowledge_prompt=prompt_info,
|
knowledge_prompt=prompt_info,
|
||||||
memory_block=memory_block,
|
memory_block=memory_block,
|
||||||
relation_info_block=relation_info,
|
relation_info_block=relation_info,
|
||||||
extra_info_block=extra_info_block,
|
extra_info_block=extra_info_block,
|
||||||
identity=identity_block,
|
identity=identity_block,
|
||||||
action_descriptions=action_descriptions,
|
action_descriptions=action_descriptions,
|
||||||
sender_name=sender,
|
sender_name=sender,
|
||||||
mood_state=mood_prompt,
|
mood_state=mood_prompt,
|
||||||
background_dialogue_prompt=background_dialogue_prompt,
|
background_dialogue_prompt=background_dialogue_prompt,
|
||||||
time_block=time_block,
|
time_block=time_block,
|
||||||
core_dialogue_prompt=core_dialogue_prompt,
|
core_dialogue_prompt=core_dialogue_prompt,
|
||||||
reply_target_block=reply_target_block,
|
reply_target_block=reply_target_block,
|
||||||
message_txt=target,
|
message_txt=target,
|
||||||
config_expression_style=global_config.expression.expression_style,
|
config_expression_style=global_config.expression.expression_style,
|
||||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
keywords_reaction_prompt=keywords_reaction_prompt,
|
||||||
moderation_prompt=moderation_prompt_block,
|
moderation_prompt=moderation_prompt_block,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
self.build_mai_think_context(
|
|
||||||
chat_id=chat_id,
|
|
||||||
memory_block=memory_block,
|
|
||||||
relation_info=relation_info,
|
|
||||||
time_block=time_block,
|
|
||||||
chat_target_1=chat_target_1,
|
|
||||||
chat_target_2=chat_target_2,
|
|
||||||
mood_prompt=mood_prompt,
|
|
||||||
identity_block=identity_block,
|
|
||||||
sender=sender,
|
|
||||||
target=target,
|
|
||||||
chat_info=chat_talking_prompt
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用原有的模式
|
|
||||||
return await global_prompt_manager.format_prompt(
|
|
||||||
template_name,
|
|
||||||
expression_habits_block=expression_habits_block,
|
|
||||||
chat_target=chat_target_1,
|
|
||||||
chat_info=chat_talking_prompt,
|
|
||||||
memory_block=memory_block,
|
|
||||||
tool_info_block=tool_info,
|
|
||||||
knowledge_prompt=prompt_info,
|
|
||||||
extra_info_block=extra_info_block,
|
|
||||||
relation_info_block=relation_info,
|
|
||||||
time_block=time_block,
|
|
||||||
reply_target_block=reply_target_block,
|
|
||||||
moderation_prompt=moderation_prompt_block,
|
|
||||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
|
||||||
identity=identity_block,
|
|
||||||
target_message=target,
|
|
||||||
sender_name=sender,
|
|
||||||
config_expression_style=global_config.expression.expression_style,
|
|
||||||
action_descriptions=action_descriptions,
|
|
||||||
chat_target_2=chat_target_2,
|
|
||||||
mood_state=mood_prompt,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def build_prompt_rewrite_context(
|
async def build_prompt_rewrite_context(
|
||||||
self,
|
self,
|
||||||
reply_data: Dict[str, Any],
|
raw_reply: str,
|
||||||
|
reason: str,
|
||||||
|
reply_to: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
chat_stream = self.chat_stream
|
chat_stream = self.chat_stream
|
||||||
chat_id = chat_stream.stream_id
|
chat_id = chat_stream.stream_id
|
||||||
is_group_chat = bool(chat_stream.group_info)
|
is_group_chat = bool(chat_stream.group_info)
|
||||||
|
|
||||||
reply_to = reply_data.get("reply_to", "none")
|
|
||||||
raw_reply = reply_data.get("raw_reply", "")
|
|
||||||
reason = reply_data.get("reason", "")
|
|
||||||
sender, target = self._parse_reply_target(reply_to)
|
sender, target = self._parse_reply_target(reply_to)
|
||||||
|
|
||||||
# 添加情绪状态获取
|
# 添加情绪状态获取
|
||||||
@@ -915,7 +874,7 @@ class DefaultReplyer:
|
|||||||
# 并行执行2个构建任务
|
# 并行执行2个构建任务
|
||||||
expression_habits_block, relation_info = await asyncio.gather(
|
expression_habits_block, relation_info = await asyncio.gather(
|
||||||
self.build_expression_habits(chat_talking_prompt_half, target),
|
self.build_expression_habits(chat_talking_prompt_half, target),
|
||||||
self.build_relation_info(reply_data),
|
self.build_relation_info(reply_to),
|
||||||
)
|
)
|
||||||
|
|
||||||
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
||||||
@@ -1018,6 +977,31 @@ class DefaultReplyer:
|
|||||||
display_message=display_message,
|
display_message=display_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def llm_generate_content(self, prompt: str) -> str:
|
||||||
|
with Timer("LLM生成", {}): # 内部计时器,可选保留
|
||||||
|
# 加权随机选择一个模型配置
|
||||||
|
selected_model_config = self._select_weighted_model_config()
|
||||||
|
model_display_name = selected_model_config.get('model_name') or selected_model_config.get('name', 'N/A')
|
||||||
|
logger.info(
|
||||||
|
f"使用模型生成回复: {model_display_name} (选中概率: {selected_model_config.get('weight', 1.0)})"
|
||||||
|
)
|
||||||
|
|
||||||
|
express_model = LLMRequest(
|
||||||
|
model=selected_model_config,
|
||||||
|
request_type=self.request_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
if global_config.debug.show_prompt:
|
||||||
|
logger.info(f"\n{prompt}\n")
|
||||||
|
else:
|
||||||
|
logger.debug(f"\n{prompt}\n")
|
||||||
|
|
||||||
|
# TODO: 这里的_应该做出替换
|
||||||
|
content, _ = await express_model.generate_response_async(prompt)
|
||||||
|
|
||||||
|
logger.debug(f"replyer生成内容: {content}")
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
def weighted_sample_no_replacement(items, weights, k) -> list:
|
def weighted_sample_no_replacement(items, weights, k) -> list:
|
||||||
"""
|
"""
|
||||||
@@ -1075,10 +1059,8 @@ async def get_prompt_info(message: str, threshold: float):
|
|||||||
related_info += found_knowledge_from_lpmm
|
related_info += found_knowledge_from_lpmm
|
||||||
logger.debug(f"获取知识库内容耗时: {(end_time - start_time):.3f}秒")
|
logger.debug(f"获取知识库内容耗时: {(end_time - start_time):.3f}秒")
|
||||||
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
|
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
|
||||||
|
|
||||||
# 格式化知识信息
|
return f"你有以下这些**知识**:\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n"
|
||||||
formatted_prompt_info = await global_prompt_manager.format_prompt("knowledge_prompt", prompt_info=related_info)
|
|
||||||
return formatted_prompt_info
|
|
||||||
else:
|
else:
|
||||||
logger.debug("从LPMM知识库获取知识失败,可能是从未导入过知识,返回空知识...")
|
logger.debug("从LPMM知识库获取知识失败,可能是从未导入过知识,返回空知识...")
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import time # 导入 time 模块以获取当前时间
|
|||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from typing import List, Dict, Any, Tuple, Optional
|
from typing import List, Dict, Any, Tuple, Optional, Callable
|
||||||
from rich.traceback import install
|
from rich.traceback import install
|
||||||
|
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
@@ -10,11 +10,161 @@ from src.common.message_repository import find_messages, count_messages
|
|||||||
from src.common.database.database_model import ActionRecords
|
from src.common.database.database_model import ActionRecords
|
||||||
from src.common.database.database_model import Images
|
from src.common.database.database_model import Images
|
||||||
from src.person_info.person_info import PersonInfoManager, get_person_info_manager
|
from src.person_info.person_info import PersonInfoManager, get_person_info_manager
|
||||||
from src.chat.utils.utils import translate_timestamp_to_human_readable,assign_message_ids
|
from src.chat.utils.utils import translate_timestamp_to_human_readable, assign_message_ids
|
||||||
|
|
||||||
install(extra_lines=3)
|
install(extra_lines=3)
|
||||||
|
|
||||||
|
|
||||||
|
def replace_user_references_sync(
|
||||||
|
content: str,
|
||||||
|
platform: str,
|
||||||
|
name_resolver: Optional[Callable[[str, str], str]] = None,
|
||||||
|
replace_bot_name: bool = True,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
替换内容中的用户引用格式,包括回复<aaa:bbb>和@<aaa:bbb>格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content: 要处理的内容字符串
|
||||||
|
platform: 平台标识
|
||||||
|
name_resolver: 名称解析函数,接收(platform, user_id)参数,返回用户名称
|
||||||
|
如果为None,则使用默认的person_info_manager
|
||||||
|
replace_bot_name: 是否将机器人的user_id替换为"机器人昵称(你)"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 处理后的内容字符串
|
||||||
|
"""
|
||||||
|
if name_resolver is None:
|
||||||
|
person_info_manager = get_person_info_manager()
|
||||||
|
|
||||||
|
def default_resolver(platform: str, user_id: str) -> str:
|
||||||
|
# 检查是否是机器人自己
|
||||||
|
if replace_bot_name and user_id == global_config.bot.qq_account:
|
||||||
|
return f"{global_config.bot.nickname}(你)"
|
||||||
|
person_id = PersonInfoManager.get_person_id(platform, user_id)
|
||||||
|
return person_info_manager.get_value_sync(person_id, "person_name") or user_id # type: ignore
|
||||||
|
|
||||||
|
name_resolver = default_resolver
|
||||||
|
|
||||||
|
# 处理回复<aaa:bbb>格式
|
||||||
|
reply_pattern = r"回复<([^:<>]+):([^:<>]+)>"
|
||||||
|
match = re.search(reply_pattern, content)
|
||||||
|
if match:
|
||||||
|
aaa = match[1]
|
||||||
|
bbb = match[2]
|
||||||
|
try:
|
||||||
|
# 检查是否是机器人自己
|
||||||
|
if replace_bot_name and bbb == global_config.bot.qq_account:
|
||||||
|
reply_person_name = f"{global_config.bot.nickname}(你)"
|
||||||
|
else:
|
||||||
|
reply_person_name = name_resolver(platform, bbb) or aaa
|
||||||
|
content = re.sub(reply_pattern, f"回复 {reply_person_name}", content, count=1)
|
||||||
|
except Exception:
|
||||||
|
# 如果解析失败,使用原始昵称
|
||||||
|
content = re.sub(reply_pattern, f"回复 {aaa}", content, count=1)
|
||||||
|
|
||||||
|
# 处理@<aaa:bbb>格式
|
||||||
|
at_pattern = r"@<([^:<>]+):([^:<>]+)>"
|
||||||
|
at_matches = list(re.finditer(at_pattern, content))
|
||||||
|
if at_matches:
|
||||||
|
new_content = ""
|
||||||
|
last_end = 0
|
||||||
|
for m in at_matches:
|
||||||
|
new_content += content[last_end : m.start()]
|
||||||
|
aaa = m.group(1)
|
||||||
|
bbb = m.group(2)
|
||||||
|
try:
|
||||||
|
# 检查是否是机器人自己
|
||||||
|
if replace_bot_name and bbb == global_config.bot.qq_account:
|
||||||
|
at_person_name = f"{global_config.bot.nickname}(你)"
|
||||||
|
else:
|
||||||
|
at_person_name = name_resolver(platform, bbb) or aaa
|
||||||
|
new_content += f"@{at_person_name}"
|
||||||
|
except Exception:
|
||||||
|
# 如果解析失败,使用原始昵称
|
||||||
|
new_content += f"@{aaa}"
|
||||||
|
last_end = m.end()
|
||||||
|
new_content += content[last_end:]
|
||||||
|
content = new_content
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
async def replace_user_references_async(
|
||||||
|
content: str,
|
||||||
|
platform: str,
|
||||||
|
name_resolver: Optional[Callable[[str, str], Any]] = None,
|
||||||
|
replace_bot_name: bool = True,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
替换内容中的用户引用格式,包括回复<aaa:bbb>和@<aaa:bbb>格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content: 要处理的内容字符串
|
||||||
|
platform: 平台标识
|
||||||
|
name_resolver: 名称解析函数,接收(platform, user_id)参数,返回用户名称
|
||||||
|
如果为None,则使用默认的person_info_manager
|
||||||
|
replace_bot_name: 是否将机器人的user_id替换为"机器人昵称(你)"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 处理后的内容字符串
|
||||||
|
"""
|
||||||
|
if name_resolver is None:
|
||||||
|
person_info_manager = get_person_info_manager()
|
||||||
|
|
||||||
|
async def default_resolver(platform: str, user_id: str) -> str:
|
||||||
|
# 检查是否是机器人自己
|
||||||
|
if replace_bot_name and user_id == global_config.bot.qq_account:
|
||||||
|
return f"{global_config.bot.nickname}(你)"
|
||||||
|
person_id = PersonInfoManager.get_person_id(platform, user_id)
|
||||||
|
return await person_info_manager.get_value(person_id, "person_name") or user_id # type: ignore
|
||||||
|
|
||||||
|
name_resolver = default_resolver
|
||||||
|
|
||||||
|
# 处理回复<aaa:bbb>格式
|
||||||
|
reply_pattern = r"回复<([^:<>]+):([^:<>]+)>"
|
||||||
|
match = re.search(reply_pattern, content)
|
||||||
|
if match:
|
||||||
|
aaa = match.group(1)
|
||||||
|
bbb = match.group(2)
|
||||||
|
try:
|
||||||
|
# 检查是否是机器人自己
|
||||||
|
if replace_bot_name and bbb == global_config.bot.qq_account:
|
||||||
|
reply_person_name = f"{global_config.bot.nickname}(你)"
|
||||||
|
else:
|
||||||
|
reply_person_name = await name_resolver(platform, bbb) or aaa
|
||||||
|
content = re.sub(reply_pattern, f"回复 {reply_person_name}", content, count=1)
|
||||||
|
except Exception:
|
||||||
|
# 如果解析失败,使用原始昵称
|
||||||
|
content = re.sub(reply_pattern, f"回复 {aaa}", content, count=1)
|
||||||
|
|
||||||
|
# 处理@<aaa:bbb>格式
|
||||||
|
at_pattern = r"@<([^:<>]+):([^:<>]+)>"
|
||||||
|
at_matches = list(re.finditer(at_pattern, content))
|
||||||
|
if at_matches:
|
||||||
|
new_content = ""
|
||||||
|
last_end = 0
|
||||||
|
for m in at_matches:
|
||||||
|
new_content += content[last_end : m.start()]
|
||||||
|
aaa = m.group(1)
|
||||||
|
bbb = m.group(2)
|
||||||
|
try:
|
||||||
|
# 检查是否是机器人自己
|
||||||
|
if replace_bot_name and bbb == global_config.bot.qq_account:
|
||||||
|
at_person_name = f"{global_config.bot.nickname}(你)"
|
||||||
|
else:
|
||||||
|
at_person_name = await name_resolver(platform, bbb) or aaa
|
||||||
|
new_content += f"@{at_person_name}"
|
||||||
|
except Exception:
|
||||||
|
# 如果解析失败,使用原始昵称
|
||||||
|
new_content += f"@{aaa}"
|
||||||
|
last_end = m.end()
|
||||||
|
new_content += content[last_end:]
|
||||||
|
content = new_content
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
def get_raw_msg_by_timestamp(
|
def get_raw_msg_by_timestamp(
|
||||||
timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest"
|
timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest"
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
@@ -374,33 +524,8 @@ def _build_readable_messages_internal(
|
|||||||
else:
|
else:
|
||||||
person_name = "某人"
|
person_name = "某人"
|
||||||
|
|
||||||
# 检查是否有 回复<aaa:bbb> 字段
|
# 使用独立函数处理用户引用格式
|
||||||
reply_pattern = r"回复<([^:<>]+):([^:<>]+)>"
|
content = replace_user_references_sync(content, platform, replace_bot_name=replace_bot_name)
|
||||||
match = re.search(reply_pattern, content)
|
|
||||||
if match:
|
|
||||||
aaa: str = match[1]
|
|
||||||
bbb: str = match[2]
|
|
||||||
reply_person_id = PersonInfoManager.get_person_id(platform, bbb)
|
|
||||||
reply_person_name = person_info_manager.get_value_sync(reply_person_id, "person_name") or aaa
|
|
||||||
# 在内容前加上回复信息
|
|
||||||
content = re.sub(reply_pattern, lambda m, name=reply_person_name: f"回复 {name}", content, count=1)
|
|
||||||
|
|
||||||
# 检查是否有 @<aaa:bbb> 字段 @<{member_info.get('nickname')}:{member_info.get('user_id')}>
|
|
||||||
at_pattern = r"@<([^:<>]+):([^:<>]+)>"
|
|
||||||
at_matches = list(re.finditer(at_pattern, content))
|
|
||||||
if at_matches:
|
|
||||||
new_content = ""
|
|
||||||
last_end = 0
|
|
||||||
for m in at_matches:
|
|
||||||
new_content += content[last_end : m.start()]
|
|
||||||
aaa = m.group(1)
|
|
||||||
bbb = m.group(2)
|
|
||||||
at_person_id = PersonInfoManager.get_person_id(platform, bbb)
|
|
||||||
at_person_name = person_info_manager.get_value_sync(at_person_id, "person_name") or aaa
|
|
||||||
new_content += f"@{at_person_name}"
|
|
||||||
last_end = m.end()
|
|
||||||
new_content += content[last_end:]
|
|
||||||
content = new_content
|
|
||||||
|
|
||||||
target_str = "这是QQ的一个功能,用于提及某人,但没那么明显"
|
target_str = "这是QQ的一个功能,用于提及某人,但没那么明显"
|
||||||
if target_str in content and random.random() < 0.6:
|
if target_str in content and random.random() < 0.6:
|
||||||
@@ -654,6 +779,7 @@ async def build_readable_messages_with_list(
|
|||||||
|
|
||||||
return formatted_string, details_list
|
return formatted_string, details_list
|
||||||
|
|
||||||
|
|
||||||
def build_readable_messages_with_id(
|
def build_readable_messages_with_id(
|
||||||
messages: List[Dict[str, Any]],
|
messages: List[Dict[str, Any]],
|
||||||
replace_bot_name: bool = True,
|
replace_bot_name: bool = True,
|
||||||
@@ -669,9 +795,9 @@ def build_readable_messages_with_id(
|
|||||||
允许通过参数控制格式化行为。
|
允许通过参数控制格式化行为。
|
||||||
"""
|
"""
|
||||||
message_id_list = assign_message_ids(messages)
|
message_id_list = assign_message_ids(messages)
|
||||||
|
|
||||||
formatted_string = build_readable_messages(
|
formatted_string = build_readable_messages(
|
||||||
messages = messages,
|
messages=messages,
|
||||||
replace_bot_name=replace_bot_name,
|
replace_bot_name=replace_bot_name,
|
||||||
merge_messages=merge_messages,
|
merge_messages=merge_messages,
|
||||||
timestamp_mode=timestamp_mode,
|
timestamp_mode=timestamp_mode,
|
||||||
@@ -682,10 +808,7 @@ def build_readable_messages_with_id(
|
|||||||
message_id_list=message_id_list,
|
message_id_list=message_id_list,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return formatted_string, message_id_list
|
||||||
|
|
||||||
|
|
||||||
return formatted_string , message_id_list
|
|
||||||
|
|
||||||
|
|
||||||
def build_readable_messages(
|
def build_readable_messages(
|
||||||
@@ -770,7 +893,13 @@ def build_readable_messages(
|
|||||||
if read_mark <= 0:
|
if read_mark <= 0:
|
||||||
# 没有有效的 read_mark,直接格式化所有消息
|
# 没有有效的 read_mark,直接格式化所有消息
|
||||||
formatted_string, _, pic_id_mapping, _ = _build_readable_messages_internal(
|
formatted_string, _, pic_id_mapping, _ = _build_readable_messages_internal(
|
||||||
copy_messages, replace_bot_name, merge_messages, timestamp_mode, truncate, show_pic=show_pic, message_id_list=message_id_list
|
copy_messages,
|
||||||
|
replace_bot_name,
|
||||||
|
merge_messages,
|
||||||
|
timestamp_mode,
|
||||||
|
truncate,
|
||||||
|
show_pic=show_pic,
|
||||||
|
message_id_list=message_id_list,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 生成图片映射信息并添加到最前面
|
# 生成图片映射信息并添加到最前面
|
||||||
@@ -893,7 +1022,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str:
|
|||||||
|
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
try:
|
try:
|
||||||
platform = msg.get("chat_info_platform")
|
platform: str = msg.get("chat_info_platform") # type: ignore
|
||||||
user_id = msg.get("user_id")
|
user_id = msg.get("user_id")
|
||||||
_timestamp = msg.get("time")
|
_timestamp = msg.get("time")
|
||||||
content: str = ""
|
content: str = ""
|
||||||
@@ -916,38 +1045,14 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str:
|
|||||||
anon_name = get_anon_name(platform, user_id)
|
anon_name = get_anon_name(platform, user_id)
|
||||||
# print(f"anon_name:{anon_name}")
|
# print(f"anon_name:{anon_name}")
|
||||||
|
|
||||||
# 处理 回复<aaa:bbb>
|
# 使用独立函数处理用户引用格式,传入自定义的匿名名称解析器
|
||||||
reply_pattern = r"回复<([^:<>]+):([^:<>]+)>"
|
def anon_name_resolver(platform: str, user_id: str) -> str:
|
||||||
match = re.search(reply_pattern, content)
|
|
||||||
if match:
|
|
||||||
# print(f"发现回复match:{match}")
|
|
||||||
bbb = match.group(2)
|
|
||||||
try:
|
try:
|
||||||
anon_reply = get_anon_name(platform, bbb)
|
return get_anon_name(platform, user_id)
|
||||||
# print(f"anon_reply:{anon_reply}")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
anon_reply = "?"
|
return "?"
|
||||||
content = re.sub(reply_pattern, f"回复 {anon_reply}", content, count=1)
|
|
||||||
|
|
||||||
# 处理 @<aaa:bbb>,无嵌套def
|
content = replace_user_references_sync(content, platform, anon_name_resolver, replace_bot_name=False)
|
||||||
at_pattern = r"@<([^:<>]+):([^:<>]+)>"
|
|
||||||
at_matches = list(re.finditer(at_pattern, content))
|
|
||||||
if at_matches:
|
|
||||||
# print(f"发现@match:{at_matches}")
|
|
||||||
new_content = ""
|
|
||||||
last_end = 0
|
|
||||||
for m in at_matches:
|
|
||||||
new_content += content[last_end : m.start()]
|
|
||||||
bbb = m.group(2)
|
|
||||||
try:
|
|
||||||
anon_at = get_anon_name(platform, bbb)
|
|
||||||
# print(f"anon_at:{anon_at}")
|
|
||||||
except Exception:
|
|
||||||
anon_at = "?"
|
|
||||||
new_content += f"@{anon_at}"
|
|
||||||
last_end = m.end()
|
|
||||||
new_content += content[last_end:]
|
|
||||||
content = new_content
|
|
||||||
|
|
||||||
header = f"{anon_name}说 "
|
header = f"{anon_name}说 "
|
||||||
output_lines.append(header)
|
output_lines.append(header)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class ImageManager:
|
|||||||
self._ensure_image_dir()
|
self._ensure_image_dir()
|
||||||
|
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
self._llm = LLMRequest(model=global_config.model.vlm, temperature=0.4, max_tokens=300, request_type="image")
|
self.vlm = LLMRequest(model=global_config.model.vlm, temperature=0.4, max_tokens=300, request_type="image")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.connect(reuse_if_open=True)
|
db.connect(reuse_if_open=True)
|
||||||
@@ -94,7 +94,7 @@ class ImageManager:
|
|||||||
logger.error(f"保存描述到数据库失败 (Peewee): {str(e)}")
|
logger.error(f"保存描述到数据库失败 (Peewee): {str(e)}")
|
||||||
|
|
||||||
async def get_emoji_description(self, image_base64: str) -> str:
|
async def get_emoji_description(self, image_base64: str) -> str:
|
||||||
"""获取表情包描述,使用二步走识别并带缓存优化"""
|
"""获取表情包描述,优先使用Emoji表中的缓存数据"""
|
||||||
try:
|
try:
|
||||||
# 计算图片哈希
|
# 计算图片哈希
|
||||||
# 确保base64字符串只包含ASCII字符
|
# 确保base64字符串只包含ASCII字符
|
||||||
@@ -104,9 +104,21 @@ class ImageManager:
|
|||||||
image_hash = hashlib.md5(image_bytes).hexdigest()
|
image_hash = hashlib.md5(image_bytes).hexdigest()
|
||||||
image_format = Image.open(io.BytesIO(image_bytes)).format.lower() # type: ignore
|
image_format = Image.open(io.BytesIO(image_bytes)).format.lower() # type: ignore
|
||||||
|
|
||||||
# 查询缓存的描述
|
# 优先使用EmojiManager查询已注册表情包的描述
|
||||||
|
try:
|
||||||
|
from src.chat.emoji_system.emoji_manager import get_emoji_manager
|
||||||
|
emoji_manager = get_emoji_manager()
|
||||||
|
cached_emoji_description = await emoji_manager.get_emoji_description_by_hash(image_hash)
|
||||||
|
if cached_emoji_description:
|
||||||
|
logger.info(f"[缓存命中] 使用已注册表情包描述: {cached_emoji_description[:50]}...")
|
||||||
|
return cached_emoji_description
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"查询EmojiManager时出错: {e}")
|
||||||
|
|
||||||
|
# 查询ImageDescriptions表的缓存描述
|
||||||
cached_description = self._get_description_from_db(image_hash, "emoji")
|
cached_description = self._get_description_from_db(image_hash, "emoji")
|
||||||
if cached_description:
|
if cached_description:
|
||||||
|
logger.info(f"[缓存命中] 使用ImageDescriptions表中的描述: {cached_description[:50]}...")
|
||||||
return f"[表情包:{cached_description}]"
|
return f"[表情包:{cached_description}]"
|
||||||
|
|
||||||
# === 二步走识别流程 ===
|
# === 二步走识别流程 ===
|
||||||
@@ -118,10 +130,10 @@ class ImageManager:
|
|||||||
logger.warning("GIF转换失败,无法获取描述")
|
logger.warning("GIF转换失败,无法获取描述")
|
||||||
return "[表情包(GIF处理失败)]"
|
return "[表情包(GIF处理失败)]"
|
||||||
vlm_prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,描述一下表情包表达的情感和内容,描述细节,从互联网梗,meme的角度去分析"
|
vlm_prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,描述一下表情包表达的情感和内容,描述细节,从互联网梗,meme的角度去分析"
|
||||||
detailed_description, _ = await self._llm.generate_response_for_image(vlm_prompt, image_base64_processed, "jpg")
|
detailed_description, _ = await self.vlm.generate_response_for_image(vlm_prompt, image_base64_processed, "jpg")
|
||||||
else:
|
else:
|
||||||
vlm_prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,描述细节,从互联网梗,meme的角度去分析"
|
vlm_prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,描述细节,从互联网梗,meme的角度去分析"
|
||||||
detailed_description, _ = await self._llm.generate_response_for_image(vlm_prompt, image_base64, image_format)
|
detailed_description, _ = await self.vlm.generate_response_for_image(vlm_prompt, image_base64, image_format)
|
||||||
|
|
||||||
if detailed_description is None:
|
if detailed_description is None:
|
||||||
logger.warning("VLM未能生成表情包详细描述")
|
logger.warning("VLM未能生成表情包详细描述")
|
||||||
@@ -158,7 +170,7 @@ class ImageManager:
|
|||||||
if len(emotions) > 1 and emotions[1] != emotions[0]:
|
if len(emotions) > 1 and emotions[1] != emotions[0]:
|
||||||
final_emotion = f"{emotions[0]},{emotions[1]}"
|
final_emotion = f"{emotions[0]},{emotions[1]}"
|
||||||
|
|
||||||
logger.info(f"[二步走识别] 详细描述: {detailed_description[:50]}... -> 情感标签: {final_emotion}")
|
logger.info(f"[emoji识别] 详细描述: {detailed_description[:50]}... -> 情感标签: {final_emotion}")
|
||||||
|
|
||||||
# 再次检查缓存,防止并发写入时重复生成
|
# 再次检查缓存,防止并发写入时重复生成
|
||||||
cached_description = self._get_description_from_db(image_hash, "emoji")
|
cached_description = self._get_description_from_db(image_hash, "emoji")
|
||||||
@@ -201,13 +213,13 @@ class ImageManager:
|
|||||||
self._save_description_to_db(image_hash, final_emotion, "emoji")
|
self._save_description_to_db(image_hash, final_emotion, "emoji")
|
||||||
|
|
||||||
return f"[表情包:{final_emotion}]"
|
return f"[表情包:{final_emotion}]"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取表情包描述失败: {str(e)}")
|
logger.error(f"获取表情包描述失败: {str(e)}")
|
||||||
return "[表情包]"
|
return "[表情包(处理失败)]"
|
||||||
|
|
||||||
async def get_image_description(self, image_base64: str) -> str:
|
async def get_image_description(self, image_base64: str) -> str:
|
||||||
"""获取普通图片描述,带查重和保存功能"""
|
"""获取普通图片描述,优先使用Images表中的缓存数据"""
|
||||||
try:
|
try:
|
||||||
# 计算图片哈希
|
# 计算图片哈希
|
||||||
if isinstance(image_base64, str):
|
if isinstance(image_base64, str):
|
||||||
@@ -215,7 +227,7 @@ class ImageManager:
|
|||||||
image_bytes = base64.b64decode(image_base64)
|
image_bytes = base64.b64decode(image_base64)
|
||||||
image_hash = hashlib.md5(image_bytes).hexdigest()
|
image_hash = hashlib.md5(image_bytes).hexdigest()
|
||||||
|
|
||||||
# 检查图片是否已存在
|
# 优先检查Images表中是否已有完整的描述
|
||||||
existing_image = Images.get_or_none(Images.emoji_hash == image_hash)
|
existing_image = Images.get_or_none(Images.emoji_hash == image_hash)
|
||||||
if existing_image:
|
if existing_image:
|
||||||
# 更新计数
|
# 更新计数
|
||||||
@@ -227,18 +239,20 @@ class ImageManager:
|
|||||||
|
|
||||||
# 如果已有描述,直接返回
|
# 如果已有描述,直接返回
|
||||||
if existing_image.description:
|
if existing_image.description:
|
||||||
|
logger.debug(f"[缓存命中] 使用Images表中的图片描述: {existing_image.description[:50]}...")
|
||||||
return f"[图片:{existing_image.description}]"
|
return f"[图片:{existing_image.description}]"
|
||||||
|
|
||||||
# 查询缓存的描述
|
# 查询ImageDescriptions表的缓存描述
|
||||||
cached_description = self._get_description_from_db(image_hash, "image")
|
cached_description = self._get_description_from_db(image_hash, "image")
|
||||||
if cached_description:
|
if cached_description:
|
||||||
logger.debug(f"图片描述缓存中 {cached_description}")
|
logger.debug(f"[缓存命中] 使用ImageDescriptions表中的描述: {cached_description[:50]}...")
|
||||||
return f"[图片:{cached_description}]"
|
return f"[图片:{cached_description}]"
|
||||||
|
|
||||||
# 调用AI获取描述
|
# 调用AI获取描述
|
||||||
image_format = Image.open(io.BytesIO(image_bytes)).format.lower() # type: ignore
|
image_format = Image.open(io.BytesIO(image_bytes)).format.lower() # type: ignore
|
||||||
prompt = global_config.custom_prompt.image_prompt
|
prompt = global_config.custom_prompt.image_prompt
|
||||||
description, _ = await self._llm.generate_response_for_image(prompt, image_base64, image_format)
|
logger.info(f"[VLM调用] 为图片生成新描述 (Hash: {image_hash[:8]}...)")
|
||||||
|
description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, image_format)
|
||||||
|
|
||||||
if description is None:
|
if description is None:
|
||||||
logger.warning("AI未能生成图片描述")
|
logger.warning("AI未能生成图片描述")
|
||||||
@@ -266,6 +280,7 @@ class ImageManager:
|
|||||||
if not hasattr(existing_image, "vlm_processed") or existing_image.vlm_processed is None:
|
if not hasattr(existing_image, "vlm_processed") or existing_image.vlm_processed is None:
|
||||||
existing_image.vlm_processed = True
|
existing_image.vlm_processed = True
|
||||||
existing_image.save()
|
existing_image.save()
|
||||||
|
logger.debug(f"[数据库] 更新已有图片记录: {image_hash[:8]}...")
|
||||||
else:
|
else:
|
||||||
Images.create(
|
Images.create(
|
||||||
image_id=str(uuid.uuid4()),
|
image_id=str(uuid.uuid4()),
|
||||||
@@ -277,16 +292,18 @@ class ImageManager:
|
|||||||
vlm_processed=True,
|
vlm_processed=True,
|
||||||
count=1,
|
count=1,
|
||||||
)
|
)
|
||||||
|
logger.debug(f"[数据库] 创建新图片记录: {image_hash[:8]}...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"保存图片文件或元数据失败: {str(e)}")
|
logger.error(f"保存图片文件或元数据失败: {str(e)}")
|
||||||
|
|
||||||
# 保存描述到ImageDescriptions表
|
# 保存描述到ImageDescriptions表作为备用缓存
|
||||||
self._save_description_to_db(image_hash, description, "image")
|
self._save_description_to_db(image_hash, description, "image")
|
||||||
|
|
||||||
|
logger.info(f"[VLM完成] 图片描述生成: {description[:50]}...")
|
||||||
return f"[图片:{description}]"
|
return f"[图片:{description}]"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取图片描述失败: {str(e)}")
|
logger.error(f"获取图片描述失败: {str(e)}")
|
||||||
return "[图片]"
|
return "[图片(处理失败)]"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def transform_gif(gif_base64: str, similarity_threshold: float = 1000.0, max_frames: int = 15) -> Optional[str]:
|
def transform_gif(gif_base64: str, similarity_threshold: float = 1000.0, max_frames: int = 15) -> Optional[str]:
|
||||||
@@ -502,12 +519,28 @@ class ImageManager:
|
|||||||
image_bytes = base64.b64decode(image_base64)
|
image_bytes = base64.b64decode(image_base64)
|
||||||
image_hash = hashlib.md5(image_bytes).hexdigest()
|
image_hash = hashlib.md5(image_bytes).hexdigest()
|
||||||
|
|
||||||
# 先检查缓存的描述
|
# 获取当前图片记录
|
||||||
|
image = Images.get(Images.image_id == image_id)
|
||||||
|
|
||||||
|
# 优先检查是否已有其他相同哈希的图片记录包含描述
|
||||||
|
existing_with_description = Images.get_or_none(
|
||||||
|
(Images.emoji_hash == image_hash) &
|
||||||
|
(Images.description.is_null(False)) &
|
||||||
|
(Images.description != "")
|
||||||
|
)
|
||||||
|
if existing_with_description and existing_with_description.id != image.id:
|
||||||
|
logger.debug(f"[缓存复用] 从其他相同图片记录复用描述: {existing_with_description.description[:50]}...")
|
||||||
|
image.description = existing_with_description.description
|
||||||
|
image.vlm_processed = True
|
||||||
|
image.save()
|
||||||
|
# 同时保存到ImageDescriptions表作为备用缓存
|
||||||
|
self._save_description_to_db(image_hash, existing_with_description.description, "image")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查ImageDescriptions表的缓存描述
|
||||||
cached_description = self._get_description_from_db(image_hash, "image")
|
cached_description = self._get_description_from_db(image_hash, "image")
|
||||||
if cached_description:
|
if cached_description:
|
||||||
logger.debug(f"VLM处理时发现缓存描述: {cached_description}")
|
logger.debug(f"[缓存复用] 从ImageDescriptions表复用描述: {cached_description[:50]}...")
|
||||||
# 更新数据库
|
|
||||||
image = Images.get(Images.image_id == image_id)
|
|
||||||
image.description = cached_description
|
image.description = cached_description
|
||||||
image.vlm_processed = True
|
image.vlm_processed = True
|
||||||
image.save()
|
image.save()
|
||||||
@@ -520,7 +553,8 @@ class ImageManager:
|
|||||||
prompt = global_config.custom_prompt.image_prompt
|
prompt = global_config.custom_prompt.image_prompt
|
||||||
|
|
||||||
# 获取VLM描述
|
# 获取VLM描述
|
||||||
description, _ = await self._llm.generate_response_for_image(prompt, image_base64, image_format)
|
logger.info(f"[VLM异步调用] 为图片生成描述 (ID: {image_id}, Hash: {image_hash[:8]}...)")
|
||||||
|
description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, image_format)
|
||||||
|
|
||||||
if description is None:
|
if description is None:
|
||||||
logger.warning("VLM未能生成图片描述")
|
logger.warning("VLM未能生成图片描述")
|
||||||
@@ -533,14 +567,15 @@ class ImageManager:
|
|||||||
description = cached_description
|
description = cached_description
|
||||||
|
|
||||||
# 更新数据库
|
# 更新数据库
|
||||||
image = Images.get(Images.image_id == image_id)
|
|
||||||
image.description = description
|
image.description = description
|
||||||
image.vlm_processed = True
|
image.vlm_processed = True
|
||||||
image.save()
|
image.save()
|
||||||
|
|
||||||
# 保存描述到ImageDescriptions表
|
# 保存描述到ImageDescriptions表作为备用缓存
|
||||||
self._save_description_to_db(image_hash, description, "image")
|
self._save_description_to_db(image_hash, description, "image")
|
||||||
|
|
||||||
|
logger.info(f"[VLM异步完成] 图片描述生成: {description[:50]}...")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"VLM处理图片失败: {str(e)}")
|
logger.error(f"VLM处理图片失败: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class ClassicalWillingManager(BaseWillingManager):
|
|||||||
|
|
||||||
# print(f"[{chat_id}] 回复意愿: {current_willing}")
|
# print(f"[{chat_id}] 回复意愿: {current_willing}")
|
||||||
|
|
||||||
interested_rate = willing_info.interested_rate * global_config.normal_chat.response_interested_rate_amplifier
|
interested_rate = willing_info.interested_rate
|
||||||
|
|
||||||
# print(f"[{chat_id}] 兴趣值: {interested_rate}")
|
# print(f"[{chat_id}] 兴趣值: {interested_rate}")
|
||||||
|
|
||||||
@@ -36,20 +36,18 @@ class ClassicalWillingManager(BaseWillingManager):
|
|||||||
current_willing += interested_rate - 0.2
|
current_willing += interested_rate - 0.2
|
||||||
|
|
||||||
if willing_info.is_mentioned_bot and global_config.chat.mentioned_bot_inevitable_reply and current_willing < 2:
|
if willing_info.is_mentioned_bot and global_config.chat.mentioned_bot_inevitable_reply and current_willing < 2:
|
||||||
current_willing += 1 if current_willing < 1.0 else 0.05
|
current_willing += 1 if current_willing < 1.0 else 0.2
|
||||||
|
|
||||||
self.chat_reply_willing[chat_id] = min(current_willing, 1.0)
|
self.chat_reply_willing[chat_id] = min(current_willing, 1.0)
|
||||||
|
|
||||||
reply_probability = min(max((current_willing - 0.5), 0.01) * 2, 1)
|
reply_probability = min(max((current_willing - 0.5), 0.01) * 2, 1.5)
|
||||||
|
|
||||||
# print(f"[{chat_id}] 回复概率: {reply_probability}")
|
# print(f"[{chat_id}] 回复概率: {reply_probability}")
|
||||||
|
|
||||||
return reply_probability
|
return reply_probability
|
||||||
|
|
||||||
async def before_generate_reply_handle(self, message_id):
|
async def before_generate_reply_handle(self, message_id):
|
||||||
chat_id = self.ongoing_messages[message_id].chat_id
|
pass
|
||||||
current_willing = self.chat_reply_willing.get(chat_id, 0)
|
|
||||||
self.chat_reply_willing[chat_id] = max(0.0, current_willing - 1.8)
|
|
||||||
|
|
||||||
async def after_generate_reply_handle(self, message_id):
|
async def after_generate_reply_handle(self, message_id):
|
||||||
if message_id not in self.ongoing_messages:
|
if message_id not in self.ongoing_messages:
|
||||||
@@ -58,7 +56,7 @@ class ClassicalWillingManager(BaseWillingManager):
|
|||||||
chat_id = self.ongoing_messages[message_id].chat_id
|
chat_id = self.ongoing_messages[message_id].chat_id
|
||||||
current_willing = self.chat_reply_willing.get(chat_id, 0)
|
current_willing = self.chat_reply_willing.get(chat_id, 0)
|
||||||
if current_willing < 1:
|
if current_willing < 1:
|
||||||
self.chat_reply_willing[chat_id] = min(1.0, current_willing + 0.4)
|
self.chat_reply_willing[chat_id] = min(1.0, current_willing + 0.3)
|
||||||
|
|
||||||
async def not_reply_handle(self, message_id):
|
async def not_reply_handle(self, message_id):
|
||||||
return await super().not_reply_handle(message_id)
|
return await super().not_reply_handle(message_id)
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ MODULE_COLORS = {
|
|||||||
"tts_action": "\033[38;5;58m", # 深黄色
|
"tts_action": "\033[38;5;58m", # 深黄色
|
||||||
"doubao_pic_plugin": "\033[38;5;64m", # 深绿色
|
"doubao_pic_plugin": "\033[38;5;64m", # 深绿色
|
||||||
# Action组件
|
# Action组件
|
||||||
"no_reply_action": "\033[38;5;196m", # 亮红色,更显眼
|
"no_reply_action": "\033[38;5;214m", # 亮橙色,显眼但不像警告
|
||||||
"reply_action": "\033[38;5;46m", # 亮绿色
|
"reply_action": "\033[38;5;46m", # 亮绿色
|
||||||
"base_action": "\033[38;5;250m", # 浅灰色
|
"base_action": "\033[38;5;250m", # 浅灰色
|
||||||
# 数据库和消息
|
# 数据库和消息
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class ChatConfig(ConfigBase):
|
|||||||
|
|
||||||
max_context_size: int = 18
|
max_context_size: int = 18
|
||||||
"""上下文长度"""
|
"""上下文长度"""
|
||||||
|
|
||||||
|
willing_amplifier: float = 1.0
|
||||||
|
|
||||||
replyer_random_probability: float = 0.5
|
replyer_random_probability: float = 0.5
|
||||||
"""
|
"""
|
||||||
@@ -76,15 +78,12 @@ class ChatConfig(ConfigBase):
|
|||||||
选择普通模型的概率为 1 - reasoning_normal_model_probability
|
选择普通模型的概率为 1 - reasoning_normal_model_probability
|
||||||
"""
|
"""
|
||||||
|
|
||||||
thinking_timeout: int = 30
|
thinking_timeout: int = 40
|
||||||
"""麦麦最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)"""
|
"""麦麦最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)"""
|
||||||
|
|
||||||
talk_frequency: float = 1
|
talk_frequency: float = 1
|
||||||
"""回复频率阈值"""
|
"""回复频率阈值"""
|
||||||
|
|
||||||
use_s4u_prompt_mode: bool = False
|
|
||||||
"""是否使用 s4u 对话构建模式,该模式会分开处理当前对话对象和其他所有对话的内容进行 prompt 构建"""
|
|
||||||
|
|
||||||
mentioned_bot_inevitable_reply: bool = False
|
mentioned_bot_inevitable_reply: bool = False
|
||||||
"""提及 bot 必然回复"""
|
"""提及 bot 必然回复"""
|
||||||
|
|
||||||
@@ -274,12 +273,6 @@ class NormalChatConfig(ConfigBase):
|
|||||||
willing_mode: str = "classical"
|
willing_mode: str = "classical"
|
||||||
"""意愿模式"""
|
"""意愿模式"""
|
||||||
|
|
||||||
response_interested_rate_amplifier: float = 1.0
|
|
||||||
"""回复兴趣度放大系数"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ExpressionConfig(ConfigBase):
|
class ExpressionConfig(ConfigBase):
|
||||||
"""表达配置类"""
|
"""表达配置类"""
|
||||||
@@ -307,11 +300,8 @@ class ExpressionConfig(ConfigBase):
|
|||||||
class ToolConfig(ConfigBase):
|
class ToolConfig(ConfigBase):
|
||||||
"""工具配置类"""
|
"""工具配置类"""
|
||||||
|
|
||||||
enable_in_normal_chat: bool = False
|
enable_tool: bool = False
|
||||||
"""是否在普通聊天中启用工具"""
|
"""是否在聊天中启用工具"""
|
||||||
|
|
||||||
enable_in_focus_chat: bool = True
|
|
||||||
"""是否在专注聊天中启用工具"""
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VoiceConfig(ConfigBase):
|
class VoiceConfig(ConfigBase):
|
||||||
|
|||||||
@@ -273,15 +273,19 @@ class Individuality:
|
|||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.strip():
|
if response and response.strip():
|
||||||
personality_parts.append(response.strip())
|
personality_parts.append(response.strip())
|
||||||
logger.info(f"精简人格侧面: {response.strip()}")
|
logger.info(f"精简人格侧面: {response.strip()}")
|
||||||
else:
|
else:
|
||||||
logger.error(f"使用LLM压缩人设时出错: {response}")
|
logger.error(f"使用LLM压缩人设时出错: {response}")
|
||||||
|
# 压缩失败时使用原始内容
|
||||||
|
if personality_side:
|
||||||
|
personality_parts.append(personality_side)
|
||||||
|
|
||||||
if personality_parts:
|
if personality_parts:
|
||||||
personality_result = "。".join(personality_parts)
|
personality_result = "。".join(personality_parts)
|
||||||
else:
|
else:
|
||||||
personality_result = personality_core
|
personality_result = personality_core or "友好活泼"
|
||||||
else:
|
else:
|
||||||
personality_result = personality_core
|
personality_result = personality_core
|
||||||
if personality_side:
|
if personality_side:
|
||||||
@@ -308,13 +312,14 @@ class Individuality:
|
|||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.strip():
|
if response and response.strip():
|
||||||
identity_result = response.strip()
|
identity_result = response.strip()
|
||||||
logger.info(f"精简身份: {identity_result}")
|
logger.info(f"精简身份: {identity_result}")
|
||||||
else:
|
else:
|
||||||
logger.error(f"使用LLM压缩身份时出错: {response}")
|
logger.error(f"使用LLM压缩身份时出错: {response}")
|
||||||
|
identity_result = identity
|
||||||
else:
|
else:
|
||||||
identity_result = "。".join(identity)
|
identity_result = identity
|
||||||
|
|
||||||
return identity_result
|
return identity_result
|
||||||
|
|
||||||
|
|||||||
@@ -47,11 +47,35 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]:
|
|||||||
logger.debug(f"记忆激活率: {interested_rate:.2f}")
|
logger.debug(f"记忆激活率: {interested_rate:.2f}")
|
||||||
|
|
||||||
text_len = len(message.processed_plain_text)
|
text_len = len(message.processed_plain_text)
|
||||||
# 根据文本长度调整兴趣度,长度越大兴趣度越高,但增长率递减,最低0.01,最高0.05
|
# 根据文本长度分布调整兴趣度,采用分段函数实现更精确的兴趣度计算
|
||||||
# 采用对数函数实现递减增长
|
# 基于实际分布:0-5字符(26.57%), 6-10字符(27.18%), 11-20字符(22.76%), 21-30字符(10.33%), 31+字符(13.86%)
|
||||||
|
|
||||||
base_interest = 0.01 + (0.05 - 0.01) * (math.log10(text_len + 1) / math.log10(1000 + 1))
|
if text_len == 0:
|
||||||
base_interest = min(max(base_interest, 0.01), 0.05)
|
base_interest = 0.01 # 空消息最低兴趣度
|
||||||
|
elif text_len <= 5:
|
||||||
|
# 1-5字符:线性增长 0.01 -> 0.03
|
||||||
|
base_interest = 0.01 + (text_len - 1) * (0.03 - 0.01) / 4
|
||||||
|
elif text_len <= 10:
|
||||||
|
# 6-10字符:线性增长 0.03 -> 0.06
|
||||||
|
base_interest = 0.03 + (text_len - 5) * (0.06 - 0.03) / 5
|
||||||
|
elif text_len <= 20:
|
||||||
|
# 11-20字符:线性增长 0.06 -> 0.12
|
||||||
|
base_interest = 0.06 + (text_len - 10) * (0.12 - 0.06) / 10
|
||||||
|
elif text_len <= 30:
|
||||||
|
# 21-30字符:线性增长 0.12 -> 0.18
|
||||||
|
base_interest = 0.12 + (text_len - 20) * (0.18 - 0.12) / 10
|
||||||
|
elif text_len <= 50:
|
||||||
|
# 31-50字符:线性增长 0.18 -> 0.22
|
||||||
|
base_interest = 0.18 + (text_len - 30) * (0.22 - 0.18) / 20
|
||||||
|
elif text_len <= 100:
|
||||||
|
# 51-100字符:线性增长 0.22 -> 0.26
|
||||||
|
base_interest = 0.22 + (text_len - 50) * (0.26 - 0.22) / 50
|
||||||
|
else:
|
||||||
|
# 100+字符:对数增长 0.26 -> 0.3,增长率递减
|
||||||
|
base_interest = 0.26 + (0.3 - 0.26) * (math.log10(text_len - 99) / math.log10(901)) # 1000-99=901
|
||||||
|
|
||||||
|
# 确保在范围内
|
||||||
|
base_interest = min(max(base_interest, 0.01), 0.3)
|
||||||
|
|
||||||
interested_rate += base_interest
|
interested_rate += base_interest
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class ChatMood:
|
|||||||
if interested_rate <= 0:
|
if interested_rate <= 0:
|
||||||
interest_multiplier = 0
|
interest_multiplier = 0
|
||||||
else:
|
else:
|
||||||
interest_multiplier = 3 * math.pow(interested_rate, 0.25)
|
interest_multiplier = 2 * math.pow(interested_rate, 0.25)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"base_probability: {base_probability}, time_multiplier: {time_multiplier}, interest_multiplier: {interest_multiplier}"
|
f"base_probability: {base_probability}, time_multiplier: {time_multiplier}, interest_multiplier: {interest_multiplier}"
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class RelationshipManager:
|
|||||||
请用json格式输出,引起了你的兴趣,或者有什么需要你记忆的点。
|
请用json格式输出,引起了你的兴趣,或者有什么需要你记忆的点。
|
||||||
并为每个点赋予1-10的权重,权重越高,表示越重要。
|
并为每个点赋予1-10的权重,权重越高,表示越重要。
|
||||||
格式如下:
|
格式如下:
|
||||||
{{
|
[
|
||||||
{{
|
{{
|
||||||
"point": "{person_name}想让我记住他的生日,我回答确认了,他的生日是11月23日",
|
"point": "{person_name}想让我记住他的生日,我回答确认了,他的生日是11月23日",
|
||||||
"weight": 10
|
"weight": 10
|
||||||
@@ -156,13 +156,10 @@ class RelationshipManager:
|
|||||||
"point": "{person_name}喜欢吃辣,具体来说,没有辣的食物ta都不喜欢吃,可能是因为ta是湖南人。",
|
"point": "{person_name}喜欢吃辣,具体来说,没有辣的食物ta都不喜欢吃,可能是因为ta是湖南人。",
|
||||||
"weight": 7
|
"weight": 7
|
||||||
}}
|
}}
|
||||||
}}
|
]
|
||||||
|
|
||||||
如果没有,就输出none,或points为空:
|
如果没有,就输出none,或返回空数组:
|
||||||
{{
|
[]
|
||||||
"point": "none",
|
|
||||||
"weight": 0
|
|
||||||
}}
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 调用LLM生成印象
|
# 调用LLM生成印象
|
||||||
@@ -184,17 +181,25 @@ class RelationshipManager:
|
|||||||
try:
|
try:
|
||||||
points = repair_json(points)
|
points = repair_json(points)
|
||||||
points_data = json.loads(points)
|
points_data = json.loads(points)
|
||||||
if points_data == "none" or not points_data or points_data.get("point") == "none":
|
|
||||||
|
# 只处理正确的格式,错误格式直接跳过
|
||||||
|
if points_data == "none" or not points_data:
|
||||||
points_list = []
|
points_list = []
|
||||||
|
elif isinstance(points_data, str) and points_data.lower() == "none":
|
||||||
|
points_list = []
|
||||||
|
elif isinstance(points_data, list):
|
||||||
|
# 正确格式:数组格式 [{"point": "...", "weight": 10}, ...]
|
||||||
|
if not points_data: # 空数组
|
||||||
|
points_list = []
|
||||||
|
else:
|
||||||
|
points_list = [(item["point"], float(item["weight"]), current_time) for item in points_data]
|
||||||
else:
|
else:
|
||||||
# logger.info(f"points_data: {points_data}")
|
# 错误格式,直接跳过不解析
|
||||||
if isinstance(points_data, dict) and "points" in points_data:
|
logger.warning(f"LLM返回了错误的JSON格式,跳过解析: {type(points_data)}, 内容: {points_data}")
|
||||||
points_data = points_data["points"]
|
points_list = []
|
||||||
if not isinstance(points_data, list):
|
|
||||||
points_data = [points_data]
|
|
||||||
# 添加可读时间到每个point
|
|
||||||
points_list = [(item["point"], float(item["weight"]), current_time) for item in points_data]
|
|
||||||
|
|
||||||
|
# 权重过滤逻辑
|
||||||
|
if points_list:
|
||||||
original_points_list = list(points_list)
|
original_points_list = list(points_list)
|
||||||
points_list.clear()
|
points_list.clear()
|
||||||
discarded_count = 0
|
discarded_count = 0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from .base import (
|
|||||||
BasePlugin,
|
BasePlugin,
|
||||||
BaseAction,
|
BaseAction,
|
||||||
BaseCommand,
|
BaseCommand,
|
||||||
|
BaseTool,
|
||||||
ConfigField,
|
ConfigField,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
ActionActivationType,
|
ActionActivationType,
|
||||||
@@ -34,6 +35,7 @@ from .utils import (
|
|||||||
|
|
||||||
from .apis import (
|
from .apis import (
|
||||||
chat_api,
|
chat_api,
|
||||||
|
tool_api,
|
||||||
component_manage_api,
|
component_manage_api,
|
||||||
config_api,
|
config_api,
|
||||||
database_api,
|
database_api,
|
||||||
@@ -44,17 +46,17 @@ from .apis import (
|
|||||||
person_api,
|
person_api,
|
||||||
plugin_manage_api,
|
plugin_manage_api,
|
||||||
send_api,
|
send_api,
|
||||||
utils_api,
|
|
||||||
register_plugin,
|
register_plugin,
|
||||||
get_logger,
|
get_logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__version__ = "1.0.0"
|
__version__ = "2.0.0"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# API 模块
|
# API 模块
|
||||||
"chat_api",
|
"chat_api",
|
||||||
|
"tool_api",
|
||||||
"component_manage_api",
|
"component_manage_api",
|
||||||
"config_api",
|
"config_api",
|
||||||
"database_api",
|
"database_api",
|
||||||
@@ -65,13 +67,13 @@ __all__ = [
|
|||||||
"person_api",
|
"person_api",
|
||||||
"plugin_manage_api",
|
"plugin_manage_api",
|
||||||
"send_api",
|
"send_api",
|
||||||
"utils_api",
|
|
||||||
"register_plugin",
|
"register_plugin",
|
||||||
"get_logger",
|
"get_logger",
|
||||||
# 基础类
|
# 基础类
|
||||||
"BasePlugin",
|
"BasePlugin",
|
||||||
"BaseAction",
|
"BaseAction",
|
||||||
"BaseCommand",
|
"BaseCommand",
|
||||||
|
"BaseTool",
|
||||||
"BaseEventHandler",
|
"BaseEventHandler",
|
||||||
# 类型定义
|
# 类型定义
|
||||||
"ComponentType",
|
"ComponentType",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from src.plugin_system.apis import (
|
|||||||
person_api,
|
person_api,
|
||||||
plugin_manage_api,
|
plugin_manage_api,
|
||||||
send_api,
|
send_api,
|
||||||
utils_api,
|
tool_api,
|
||||||
)
|
)
|
||||||
from .logging_api import get_logger
|
from .logging_api import get_logger
|
||||||
from .plugin_register_api import register_plugin
|
from .plugin_register_api import register_plugin
|
||||||
@@ -35,7 +35,7 @@ __all__ = [
|
|||||||
"person_api",
|
"person_api",
|
||||||
"plugin_manage_api",
|
"plugin_manage_api",
|
||||||
"send_api",
|
"send_api",
|
||||||
"utils_api",
|
|
||||||
"get_logger",
|
"get_logger",
|
||||||
"register_plugin",
|
"register_plugin",
|
||||||
|
"tool_api",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class ChatManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
|
# sourcery skip: for-append-to-extend
|
||||||
"""获取所有聊天流
|
"""获取所有聊天流
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -57,6 +58,7 @@ class ChatManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
|
# sourcery skip: for-append-to-extend
|
||||||
"""获取所有群聊聊天流
|
"""获取所有群聊聊天流
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -79,6 +81,7 @@ class ChatManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
|
# sourcery skip: for-append-to-extend
|
||||||
"""获取所有私聊聊天流
|
"""获取所有私聊聊天流
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -105,7 +108,7 @@ class ChatManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_group_stream_by_group_id(
|
def get_group_stream_by_group_id(
|
||||||
group_id: str, platform: Optional[str] | SpecialTypes = "qq"
|
group_id: str, platform: Optional[str] | SpecialTypes = "qq"
|
||||||
) -> Optional[ChatStream]:
|
) -> Optional[ChatStream]: # sourcery skip: remove-unnecessary-cast
|
||||||
"""根据群ID获取聊天流
|
"""根据群ID获取聊天流
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -142,7 +145,7 @@ class ChatManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_private_stream_by_user_id(
|
def get_private_stream_by_user_id(
|
||||||
user_id: str, platform: Optional[str] | SpecialTypes = "qq"
|
user_id: str, platform: Optional[str] | SpecialTypes = "qq"
|
||||||
) -> Optional[ChatStream]:
|
) -> Optional[ChatStream]: # sourcery skip: remove-unnecessary-cast
|
||||||
"""根据用户ID获取私聊流
|
"""根据用户ID获取私聊流
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -207,7 +210,7 @@ class ChatManager:
|
|||||||
chat_stream: 聊天流对象
|
chat_stream: 聊天流对象
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, Any]: 聊天流信息字典
|
Dict ({str: Any}): 聊天流信息字典
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TypeError: 如果 chat_stream 不是 ChatStream 类型
|
TypeError: 如果 chat_stream 不是 ChatStream 类型
|
||||||
@@ -282,41 +285,41 @@ class ChatManager:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
def get_all_streams(platform: Optional[str] | SpecialTypes = "qq"):
|
def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
"""获取所有聊天流的便捷函数"""
|
"""获取所有聊天流的便捷函数"""
|
||||||
return ChatManager.get_all_streams(platform)
|
return ChatManager.get_all_streams(platform)
|
||||||
|
|
||||||
|
|
||||||
def get_group_streams(platform: Optional[str] | SpecialTypes = "qq"):
|
def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
"""获取群聊聊天流的便捷函数"""
|
"""获取群聊聊天流的便捷函数"""
|
||||||
return ChatManager.get_group_streams(platform)
|
return ChatManager.get_group_streams(platform)
|
||||||
|
|
||||||
|
|
||||||
def get_private_streams(platform: Optional[str] | SpecialTypes = "qq"):
|
def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||||
"""获取私聊聊天流的便捷函数"""
|
"""获取私聊聊天流的便捷函数"""
|
||||||
return ChatManager.get_private_streams(platform)
|
return ChatManager.get_private_streams(platform)
|
||||||
|
|
||||||
|
|
||||||
def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq"):
|
def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
|
||||||
"""根据群ID获取聊天流的便捷函数"""
|
"""根据群ID获取聊天流的便捷函数"""
|
||||||
return ChatManager.get_group_stream_by_group_id(group_id, platform)
|
return ChatManager.get_group_stream_by_group_id(group_id, platform)
|
||||||
|
|
||||||
|
|
||||||
def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq"):
|
def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
|
||||||
"""根据用户ID获取私聊流的便捷函数"""
|
"""根据用户ID获取私聊流的便捷函数"""
|
||||||
return ChatManager.get_private_stream_by_user_id(user_id, platform)
|
return ChatManager.get_private_stream_by_user_id(user_id, platform)
|
||||||
|
|
||||||
|
|
||||||
def get_stream_type(chat_stream: ChatStream):
|
def get_stream_type(chat_stream: ChatStream) -> str:
|
||||||
"""获取聊天流类型的便捷函数"""
|
"""获取聊天流类型的便捷函数"""
|
||||||
return ChatManager.get_stream_type(chat_stream)
|
return ChatManager.get_stream_type(chat_stream)
|
||||||
|
|
||||||
|
|
||||||
def get_stream_info(chat_stream: ChatStream):
|
def get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]:
|
||||||
"""获取聊天流信息的便捷函数"""
|
"""获取聊天流信息的便捷函数"""
|
||||||
return ChatManager.get_stream_info(chat_stream)
|
return ChatManager.get_stream_info(chat_stream)
|
||||||
|
|
||||||
|
|
||||||
def get_streams_summary():
|
def get_streams_summary() -> Dict[str, int]:
|
||||||
"""获取聊天流统计摘要的便捷函数"""
|
"""获取聊天流统计摘要的便捷函数"""
|
||||||
return ChatManager.get_streams_summary()
|
return ChatManager.get_streams_summary()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from src.plugin_system.base.component_types import (
|
|||||||
EventHandlerInfo,
|
EventHandlerInfo,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
|
ToolInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -119,6 +120,21 @@ def get_registered_command_info(command_name: str) -> Optional[CommandInfo]:
|
|||||||
return component_registry.get_registered_command_info(command_name)
|
return component_registry.get_registered_command_info(command_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_tool_info(tool_name: str) -> Optional[ToolInfo]:
|
||||||
|
"""
|
||||||
|
获取指定 Tool 的注册信息。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name (str): Tool 名称。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ToolInfo: Tool 信息对象,如果 Tool 不存在则返回 None。
|
||||||
|
"""
|
||||||
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
|
|
||||||
|
return component_registry.get_registered_tool_info(tool_name)
|
||||||
|
|
||||||
|
|
||||||
# === EventHandler 特定查询方法 ===
|
# === EventHandler 特定查询方法 ===
|
||||||
def get_registered_event_handler_info(
|
def get_registered_event_handler_info(
|
||||||
event_handler_name: str,
|
event_handler_name: str,
|
||||||
@@ -191,6 +207,8 @@ def locally_enable_component(component_name: str, component_type: ComponentType,
|
|||||||
return global_announcement_manager.enable_specific_chat_action(stream_id, component_name)
|
return global_announcement_manager.enable_specific_chat_action(stream_id, component_name)
|
||||||
case ComponentType.COMMAND:
|
case ComponentType.COMMAND:
|
||||||
return global_announcement_manager.enable_specific_chat_command(stream_id, component_name)
|
return global_announcement_manager.enable_specific_chat_command(stream_id, component_name)
|
||||||
|
case ComponentType.TOOL:
|
||||||
|
return global_announcement_manager.enable_specific_chat_tool(stream_id, component_name)
|
||||||
case ComponentType.EVENT_HANDLER:
|
case ComponentType.EVENT_HANDLER:
|
||||||
return global_announcement_manager.enable_specific_chat_event_handler(stream_id, component_name)
|
return global_announcement_manager.enable_specific_chat_event_handler(stream_id, component_name)
|
||||||
case _:
|
case _:
|
||||||
@@ -216,11 +234,14 @@ def locally_disable_component(component_name: str, component_type: ComponentType
|
|||||||
return global_announcement_manager.disable_specific_chat_action(stream_id, component_name)
|
return global_announcement_manager.disable_specific_chat_action(stream_id, component_name)
|
||||||
case ComponentType.COMMAND:
|
case ComponentType.COMMAND:
|
||||||
return global_announcement_manager.disable_specific_chat_command(stream_id, component_name)
|
return global_announcement_manager.disable_specific_chat_command(stream_id, component_name)
|
||||||
|
case ComponentType.TOOL:
|
||||||
|
return global_announcement_manager.disable_specific_chat_tool(stream_id, component_name)
|
||||||
case ComponentType.EVENT_HANDLER:
|
case ComponentType.EVENT_HANDLER:
|
||||||
return global_announcement_manager.disable_specific_chat_event_handler(stream_id, component_name)
|
return global_announcement_manager.disable_specific_chat_event_handler(stream_id, component_name)
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"未知 component type: {component_type}")
|
raise ValueError(f"未知 component type: {component_type}")
|
||||||
|
|
||||||
|
|
||||||
def get_locally_disabled_components(stream_id: str, component_type: ComponentType) -> list[str]:
|
def get_locally_disabled_components(stream_id: str, component_type: ComponentType) -> list[str]:
|
||||||
"""
|
"""
|
||||||
获取指定消息流中禁用的组件列表。
|
获取指定消息流中禁用的组件列表。
|
||||||
@@ -239,7 +260,9 @@ def get_locally_disabled_components(stream_id: str, component_type: ComponentTyp
|
|||||||
return global_announcement_manager.get_disabled_chat_actions(stream_id)
|
return global_announcement_manager.get_disabled_chat_actions(stream_id)
|
||||||
case ComponentType.COMMAND:
|
case ComponentType.COMMAND:
|
||||||
return global_announcement_manager.get_disabled_chat_commands(stream_id)
|
return global_announcement_manager.get_disabled_chat_commands(stream_id)
|
||||||
|
case ComponentType.TOOL:
|
||||||
|
return global_announcement_manager.get_disabled_chat_tools(stream_id)
|
||||||
case ComponentType.EVENT_HANDLER:
|
case ComponentType.EVENT_HANDLER:
|
||||||
return global_announcement_manager.get_disabled_chat_event_handlers(stream_id)
|
return global_announcement_manager.get_disabled_chat_event_handlers(stream_id)
|
||||||
case _:
|
case _:
|
||||||
raise ValueError(f"未知 component type: {component_type}")
|
raise ValueError(f"未知 component type: {component_type}")
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.person_info.person_info import get_person_info_manager
|
|
||||||
|
|
||||||
logger = get_logger("config_api")
|
logger = get_logger("config_api")
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ def get_global_config(key: str, default: Any = None) -> Any:
|
|||||||
插件应使用此方法读取全局配置,以保证只读和隔离性。
|
插件应使用此方法读取全局配置,以保证只读和隔离性。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: 命名空间式配置键名,支持嵌套访问,如 "section.subsection.key",大小写敏感
|
key: 命名空间式配置键名,使用嵌套访问,如 "section.subsection.key",大小写敏感
|
||||||
default: 如果配置不存在时返回的默认值
|
default: 如果配置不存在时返回的默认值
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -76,50 +75,3 @@ def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"[ConfigAPI] 获取插件配置 {key} 失败: {e}")
|
logger.warning(f"[ConfigAPI] 获取插件配置 {key} 失败: {e}")
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# 用户信息API函数
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
async def get_user_id_by_person_name(person_name: str) -> tuple[str, str]:
|
|
||||||
"""根据内部用户名获取用户ID
|
|
||||||
|
|
||||||
Args:
|
|
||||||
person_name: 用户名
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple[str, str]: (平台, 用户ID)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
person_info_manager = get_person_info_manager()
|
|
||||||
person_id = person_info_manager.get_person_id_by_person_name(person_name)
|
|
||||||
user_id: str = await person_info_manager.get_value(person_id, "user_id") # type: ignore
|
|
||||||
platform: str = await person_info_manager.get_value(person_id, "platform") # type: ignore
|
|
||||||
return platform, user_id
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[ConfigAPI] 根据用户名获取用户ID失败: {e}")
|
|
||||||
return "", ""
|
|
||||||
|
|
||||||
|
|
||||||
async def get_person_info(person_id: str, key: str, default: Any = None) -> Any:
|
|
||||||
"""获取用户信息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
person_id: 用户ID
|
|
||||||
key: 信息键名
|
|
||||||
default: 默认值
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Any: 用户信息值或默认值
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
person_info_manager = get_person_info_manager()
|
|
||||||
response = await person_info_manager.get_value(person_id, key)
|
|
||||||
if not response:
|
|
||||||
raise ValueError(f"[ConfigAPI] 获取用户 {person_id} 的信息 '{key}' 失败,返回默认值")
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[ConfigAPI] 获取用户信息失败: {e}")
|
|
||||||
return default
|
|
||||||
|
|||||||
@@ -152,10 +152,7 @@ async def db_query(
|
|||||||
|
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
# 记录不存在
|
# 记录不存在
|
||||||
if query_type == "get" and single_result:
|
return None if query_type == "get" and single_result else []
|
||||||
return None
|
|
||||||
return []
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[DatabaseAPI] 数据库操作出错: {e}")
|
logger.error(f"[DatabaseAPI] 数据库操作出错: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -170,7 +167,8 @@ async def db_query(
|
|||||||
|
|
||||||
async def db_save(
|
async def db_save(
|
||||||
model_class: Type[Model], data: Dict[str, Any], key_field: Optional[str] = None, key_value: Optional[Any] = None
|
model_class: Type[Model], data: Dict[str, Any], key_field: Optional[str] = None, key_value: Optional[Any] = None
|
||||||
) -> Union[Dict[str, Any], None]:
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
# sourcery skip: inline-immediately-returned-variable
|
||||||
"""保存数据到数据库(创建或更新)
|
"""保存数据到数据库(创建或更新)
|
||||||
|
|
||||||
如果提供了key_field和key_value,会先尝试查找匹配的记录进行更新;
|
如果提供了key_field和key_value,会先尝试查找匹配的记录进行更新;
|
||||||
@@ -203,10 +201,9 @@ async def db_save(
|
|||||||
try:
|
try:
|
||||||
# 如果提供了key_field和key_value,尝试更新现有记录
|
# 如果提供了key_field和key_value,尝试更新现有记录
|
||||||
if key_field and key_value is not None:
|
if key_field and key_value is not None:
|
||||||
# 查找现有记录
|
if existing_records := list(
|
||||||
existing_records = list(model_class.select().where(getattr(model_class, key_field) == key_value).limit(1))
|
model_class.select().where(getattr(model_class, key_field) == key_value).limit(1)
|
||||||
|
):
|
||||||
if existing_records:
|
|
||||||
# 更新现有记录
|
# 更新现有记录
|
||||||
existing_record = existing_records[0]
|
existing_record = existing_records[0]
|
||||||
for field, value in data.items():
|
for field, value in data.items():
|
||||||
@@ -244,8 +241,8 @@ async def db_get(
|
|||||||
Args:
|
Args:
|
||||||
model_class: Peewee模型类
|
model_class: Peewee模型类
|
||||||
filters: 过滤条件,字段名和值的字典
|
filters: 过滤条件,字段名和值的字典
|
||||||
order_by: 排序字段,前缀'-'表示降序,例如'-time'表示按时间字段(即time字段)降序
|
|
||||||
limit: 结果数量限制
|
limit: 结果数量限制
|
||||||
|
order_by: 排序字段,前缀'-'表示降序,例如'-time'表示按时间字段(即time字段)降序
|
||||||
single_result: 是否只返回单个结果,如果为True,则返回单个记录字典或None;否则返回记录字典列表或空列表
|
single_result: 是否只返回单个结果,如果为True,则返回单个记录字典或None;否则返回记录字典列表或空列表
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -310,7 +307,7 @@ async def store_action_info(
|
|||||||
thinking_id: str = "",
|
thinking_id: str = "",
|
||||||
action_data: Optional[dict] = None,
|
action_data: Optional[dict] = None,
|
||||||
action_name: str = "",
|
action_name: str = "",
|
||||||
) -> Union[Dict[str, Any], None]:
|
) -> Optional[Dict[str, Any]]:
|
||||||
"""存储动作信息到数据库
|
"""存储动作信息到数据库
|
||||||
|
|
||||||
将Action执行的相关信息保存到ActionRecords表中,用于后续的记忆和上下文构建。
|
将Action执行的相关信息保存到ActionRecords表中,用于后续的记忆和上下文构建。
|
||||||
|
|||||||
@@ -65,14 +65,14 @@ async def get_by_description(description: str) -> Optional[Tuple[str, str, str]]
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str, str]]]:
|
async def get_random(count: Optional[int] = 1) -> List[Tuple[str, str, str]]:
|
||||||
"""随机获取指定数量的表情包
|
"""随机获取指定数量的表情包
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
count: 要获取的表情包数量,默认为1
|
count: 要获取的表情包数量,默认为1
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Optional[List[Tuple[str, str, str]]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,如果失败则为None
|
List[Tuple[str, str, str]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,失败则返回空列表
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TypeError: 如果count不是整数类型
|
TypeError: 如果count不是整数类型
|
||||||
@@ -94,13 +94,13 @@ async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str,
|
|||||||
|
|
||||||
if not all_emojis:
|
if not all_emojis:
|
||||||
logger.warning("[EmojiAPI] 没有可用的表情包")
|
logger.warning("[EmojiAPI] 没有可用的表情包")
|
||||||
return None
|
return []
|
||||||
|
|
||||||
# 过滤有效表情包
|
# 过滤有效表情包
|
||||||
valid_emojis = [emoji for emoji in all_emojis if not emoji.is_deleted]
|
valid_emojis = [emoji for emoji in all_emojis if not emoji.is_deleted]
|
||||||
if not valid_emojis:
|
if not valid_emojis:
|
||||||
logger.warning("[EmojiAPI] 没有有效的表情包")
|
logger.warning("[EmojiAPI] 没有有效的表情包")
|
||||||
return None
|
return []
|
||||||
|
|
||||||
if len(valid_emojis) < count:
|
if len(valid_emojis) < count:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -127,14 +127,14 @@ async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str,
|
|||||||
|
|
||||||
if not results and count > 0:
|
if not results and count > 0:
|
||||||
logger.warning("[EmojiAPI] 随机获取表情包失败,没有一个可以成功处理")
|
logger.warning("[EmojiAPI] 随机获取表情包失败,没有一个可以成功处理")
|
||||||
return None
|
return []
|
||||||
|
|
||||||
logger.info(f"[EmojiAPI] 成功获取 {len(results)} 个随机表情包")
|
logger.info(f"[EmojiAPI] 成功获取 {len(results)} 个随机表情包")
|
||||||
return results
|
return results
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[EmojiAPI] 获取随机表情包失败: {e}")
|
logger.error(f"[EmojiAPI] 获取随机表情包失败: {e}")
|
||||||
return None
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]:
|
async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]:
|
||||||
@@ -162,10 +162,11 @@ async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]:
|
|||||||
|
|
||||||
# 筛选匹配情感的表情包
|
# 筛选匹配情感的表情包
|
||||||
matching_emojis = []
|
matching_emojis = []
|
||||||
for emoji_obj in all_emojis:
|
matching_emojis.extend(
|
||||||
if not emoji_obj.is_deleted and emotion.lower() in [e.lower() for e in emoji_obj.emotion]:
|
emoji_obj
|
||||||
matching_emojis.append(emoji_obj)
|
for emoji_obj in all_emojis
|
||||||
|
if not emoji_obj.is_deleted and emotion.lower() in [e.lower() for e in emoji_obj.emotion]
|
||||||
|
)
|
||||||
if not matching_emojis:
|
if not matching_emojis:
|
||||||
logger.warning(f"[EmojiAPI] 未找到匹配情感 '{emotion}' 的表情包")
|
logger.warning(f"[EmojiAPI] 未找到匹配情感 '{emotion}' 的表情包")
|
||||||
return None
|
return None
|
||||||
@@ -256,10 +257,11 @@ def get_descriptions() -> List[str]:
|
|||||||
emoji_manager = get_emoji_manager()
|
emoji_manager = get_emoji_manager()
|
||||||
descriptions = []
|
descriptions = []
|
||||||
|
|
||||||
for emoji_obj in emoji_manager.emoji_objects:
|
descriptions.extend(
|
||||||
if not emoji_obj.is_deleted and emoji_obj.description:
|
emoji_obj.description
|
||||||
descriptions.append(emoji_obj.description)
|
for emoji_obj in emoji_manager.emoji_objects
|
||||||
|
if not emoji_obj.is_deleted and emoji_obj.description
|
||||||
|
)
|
||||||
return descriptions
|
return descriptions
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[EmojiAPI] 获取表情包描述失败: {e}")
|
logger.error(f"[EmojiAPI] 获取表情包描述失败: {e}")
|
||||||
|
|||||||
@@ -84,18 +84,23 @@ async def generate_reply(
|
|||||||
enable_chinese_typo: bool = True,
|
enable_chinese_typo: bool = True,
|
||||||
return_prompt: bool = False,
|
return_prompt: bool = False,
|
||||||
model_configs: Optional[List[Dict[str, Any]]] = None,
|
model_configs: Optional[List[Dict[str, Any]]] = None,
|
||||||
request_type: str = "",
|
request_type: str = "generator_api",
|
||||||
enable_timeout: bool = False,
|
|
||||||
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
|
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
|
||||||
"""生成回复
|
"""生成回复
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_stream: 聊天流对象(优先)
|
chat_stream: 聊天流对象(优先)
|
||||||
chat_id: 聊天ID(备用)
|
chat_id: 聊天ID(备用)
|
||||||
action_data: 动作数据
|
action_data: 动作数据(向下兼容,包含reply_to和extra_info)
|
||||||
|
reply_to: 回复对象,格式为 "发送者:消息内容"
|
||||||
|
extra_info: 额外信息,用于补充上下文
|
||||||
|
available_actions: 可用动作
|
||||||
|
enable_tool: 是否启用工具调用
|
||||||
enable_splitter: 是否启用消息分割器
|
enable_splitter: 是否启用消息分割器
|
||||||
enable_chinese_typo: 是否启用错字生成器
|
enable_chinese_typo: 是否启用错字生成器
|
||||||
return_prompt: 是否返回提示词
|
return_prompt: 是否返回提示词
|
||||||
|
model_configs: 模型配置列表
|
||||||
|
request_type: 请求类型(可选,记录LLM使用)
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[bool, List[Tuple[str, Any]], Optional[str]]: (是否成功, 回复集合, 提示词)
|
Tuple[bool, List[Tuple[str, Any]], Optional[str]]: (是否成功, 回复集合, 提示词)
|
||||||
"""
|
"""
|
||||||
@@ -108,13 +113,16 @@ async def generate_reply(
|
|||||||
|
|
||||||
logger.debug("[GeneratorAPI] 开始生成回复")
|
logger.debug("[GeneratorAPI] 开始生成回复")
|
||||||
|
|
||||||
|
if not reply_to and action_data:
|
||||||
|
reply_to = action_data.get("reply_to", "")
|
||||||
|
if not extra_info and action_data:
|
||||||
|
extra_info = action_data.get("extra_info", "")
|
||||||
|
|
||||||
# 调用回复器生成回复
|
# 调用回复器生成回复
|
||||||
success, content, prompt = await replyer.generate_reply_with_context(
|
success, content, prompt = await replyer.generate_reply_with_context(
|
||||||
reply_data=action_data or {},
|
|
||||||
reply_to=reply_to,
|
reply_to=reply_to,
|
||||||
extra_info=extra_info,
|
extra_info=extra_info,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
enable_timeout=enable_timeout,
|
|
||||||
enable_tool=enable_tool,
|
enable_tool=enable_tool,
|
||||||
)
|
)
|
||||||
reply_set = []
|
reply_set = []
|
||||||
@@ -136,6 +144,7 @@ async def generate_reply(
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[GeneratorAPI] 生成回复时出错: {e}")
|
logger.error(f"[GeneratorAPI] 生成回复时出错: {e}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
return False, [], None
|
return False, [], None
|
||||||
|
|
||||||
|
|
||||||
@@ -146,15 +155,24 @@ async def rewrite_reply(
|
|||||||
enable_splitter: bool = True,
|
enable_splitter: bool = True,
|
||||||
enable_chinese_typo: bool = True,
|
enable_chinese_typo: bool = True,
|
||||||
model_configs: Optional[List[Dict[str, Any]]] = None,
|
model_configs: Optional[List[Dict[str, Any]]] = None,
|
||||||
) -> Tuple[bool, List[Tuple[str, Any]]]:
|
raw_reply: str = "",
|
||||||
|
reason: str = "",
|
||||||
|
reply_to: str = "",
|
||||||
|
return_prompt: bool = False,
|
||||||
|
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
|
||||||
"""重写回复
|
"""重写回复
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_stream: 聊天流对象(优先)
|
chat_stream: 聊天流对象(优先)
|
||||||
reply_data: 回复数据
|
reply_data: 回复数据字典(向下兼容备用,当其他参数缺失时从此获取)
|
||||||
chat_id: 聊天ID(备用)
|
chat_id: 聊天ID(备用)
|
||||||
enable_splitter: 是否启用消息分割器
|
enable_splitter: 是否启用消息分割器
|
||||||
enable_chinese_typo: 是否启用错字生成器
|
enable_chinese_typo: 是否启用错字生成器
|
||||||
|
model_configs: 模型配置列表
|
||||||
|
raw_reply: 原始回复内容
|
||||||
|
reason: 回复原因
|
||||||
|
reply_to: 回复对象
|
||||||
|
return_prompt: 是否返回提示词
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[bool, List[Tuple[str, Any]]]: (是否成功, 回复集合)
|
Tuple[bool, List[Tuple[str, Any]]]: (是否成功, 回复集合)
|
||||||
@@ -164,12 +182,23 @@ async def rewrite_reply(
|
|||||||
replyer = get_replyer(chat_stream, chat_id, model_configs=model_configs)
|
replyer = get_replyer(chat_stream, chat_id, model_configs=model_configs)
|
||||||
if not replyer:
|
if not replyer:
|
||||||
logger.error("[GeneratorAPI] 无法获取回复器")
|
logger.error("[GeneratorAPI] 无法获取回复器")
|
||||||
return False, []
|
return False, [], None
|
||||||
|
|
||||||
logger.info("[GeneratorAPI] 开始重写回复")
|
logger.info("[GeneratorAPI] 开始重写回复")
|
||||||
|
|
||||||
|
# 如果参数缺失,从reply_data中获取
|
||||||
|
if reply_data:
|
||||||
|
raw_reply = raw_reply or reply_data.get("raw_reply", "")
|
||||||
|
reason = reason or reply_data.get("reason", "")
|
||||||
|
reply_to = reply_to or reply_data.get("reply_to", "")
|
||||||
|
|
||||||
# 调用回复器重写回复
|
# 调用回复器重写回复
|
||||||
success, content = await replyer.rewrite_reply_with_context(reply_data=reply_data or {})
|
success, content, prompt = await replyer.rewrite_reply_with_context(
|
||||||
|
raw_reply=raw_reply,
|
||||||
|
reason=reason,
|
||||||
|
reply_to=reply_to,
|
||||||
|
return_prompt=return_prompt,
|
||||||
|
)
|
||||||
reply_set = []
|
reply_set = []
|
||||||
if content:
|
if content:
|
||||||
reply_set = await process_human_text(content, enable_splitter, enable_chinese_typo)
|
reply_set = await process_human_text(content, enable_splitter, enable_chinese_typo)
|
||||||
@@ -179,14 +208,14 @@ async def rewrite_reply(
|
|||||||
else:
|
else:
|
||||||
logger.warning("[GeneratorAPI] 重写回复失败")
|
logger.warning("[GeneratorAPI] 重写回复失败")
|
||||||
|
|
||||||
return success, reply_set
|
return success, reply_set, prompt if return_prompt else None
|
||||||
|
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
raise ve
|
raise ve
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[GeneratorAPI] 重写回复时出错: {e}")
|
logger.error(f"[GeneratorAPI] 重写回复时出错: {e}")
|
||||||
return False, []
|
return False, [], None
|
||||||
|
|
||||||
|
|
||||||
async def process_human_text(content: str, enable_splitter: bool, enable_chinese_typo: bool) -> List[Tuple[str, Any]]:
|
async def process_human_text(content: str, enable_splitter: bool, enable_chinese_typo: bool) -> List[Tuple[str, Any]]:
|
||||||
@@ -212,3 +241,27 @@ async def process_human_text(content: str, enable_splitter: bool, enable_chinese
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[GeneratorAPI] 处理人形文本时出错: {e}")
|
logger.error(f"[GeneratorAPI] 处理人形文本时出错: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def generate_response_custom(
|
||||||
|
chat_stream: Optional[ChatStream] = None,
|
||||||
|
chat_id: Optional[str] = None,
|
||||||
|
model_configs: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
prompt: str = "",
|
||||||
|
) -> Optional[str]:
|
||||||
|
replyer = get_replyer(chat_stream, chat_id, model_configs=model_configs)
|
||||||
|
if not replyer:
|
||||||
|
logger.error("[GeneratorAPI] 无法获取回复器")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.debug("[GeneratorAPI] 开始生成自定义回复")
|
||||||
|
response = await replyer.llm_generate_content(prompt)
|
||||||
|
if response:
|
||||||
|
logger.debug("[GeneratorAPI] 自定义回复生成成功")
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
logger.warning("[GeneratorAPI] 自定义回复生成失败")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[GeneratorAPI] 生成自定义回复时出错: {e}")
|
||||||
|
return None
|
||||||
@@ -54,7 +54,7 @@ def get_available_models() -> Dict[str, Any]:
|
|||||||
|
|
||||||
async def generate_with_model(
|
async def generate_with_model(
|
||||||
prompt: str, model_config: Dict[str, Any], request_type: str = "plugin.generate", **kwargs
|
prompt: str, model_config: Dict[str, Any], request_type: str = "plugin.generate", **kwargs
|
||||||
) -> Tuple[bool, str, str, str]:
|
) -> Tuple[bool, str]:
|
||||||
"""使用指定模型生成内容
|
"""使用指定模型生成内容
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -73,10 +73,11 @@ async def generate_with_model(
|
|||||||
|
|
||||||
llm_request = LLMRequest(model=model_config, request_type=request_type, **kwargs)
|
llm_request = LLMRequest(model=model_config, request_type=request_type, **kwargs)
|
||||||
|
|
||||||
response, (reasoning, model_name) = await llm_request.generate_response_async(prompt)
|
# TODO: 复活这个_
|
||||||
return True, response, reasoning, model_name
|
response, _ = await llm_request.generate_response_async(prompt)
|
||||||
|
return True, response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"生成内容时出错: {str(e)}"
|
error_msg = f"生成内容时出错: {str(e)}"
|
||||||
logger.error(f"[LLMAPI] {error_msg}")
|
logger.error(f"[LLMAPI] {error_msg}")
|
||||||
return False, error_msg, "", ""
|
return False, error_msg
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ def get_random_chat_messages(
|
|||||||
|
|
||||||
|
|
||||||
def get_messages_by_time_for_users(
|
def get_messages_by_time_for_users(
|
||||||
start_time: float, end_time: float, person_ids: list, limit: int = 0, limit_mode: str = "latest"
|
start_time: float, end_time: float, person_ids: List[str], limit: int = 0, limit_mode: str = "latest"
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
获取指定用户在所有聊天中指定时间范围内的消息
|
获取指定用户在所有聊天中指定时间范围内的消息
|
||||||
@@ -287,7 +287,7 @@ def get_messages_before_time_in_chat(
|
|||||||
return get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit)
|
return get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit)
|
||||||
|
|
||||||
|
|
||||||
def get_messages_before_time_for_users(timestamp: float, person_ids: list, limit: int = 0) -> List[Dict[str, Any]]:
|
def get_messages_before_time_for_users(timestamp: float, person_ids: List[str], limit: int = 0) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
获取指定用户在指定时间戳之前的消息
|
获取指定用户在指定时间戳之前的消息
|
||||||
|
|
||||||
@@ -372,7 +372,7 @@ def count_new_messages(chat_id: str, start_time: float = 0.0, end_time: Optional
|
|||||||
return num_new_messages_since(chat_id, start_time, end_time)
|
return num_new_messages_since(chat_id, start_time, end_time)
|
||||||
|
|
||||||
|
|
||||||
def count_new_messages_for_users(chat_id: str, start_time: float, end_time: float, person_ids: list) -> int:
|
def count_new_messages_for_users(chat_id: str, start_time: float, end_time: float, person_ids: List[str]) -> int:
|
||||||
"""
|
"""
|
||||||
计算指定聊天中指定用户从开始时间到结束时间的新消息数量
|
计算指定聊天中指定用户从开始时间到结束时间的新消息数量
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
|
|
||||||
|
|
||||||
def list_loaded_plugins() -> List[str]:
|
def list_loaded_plugins() -> List[str]:
|
||||||
"""
|
"""
|
||||||
列出所有当前加载的插件。
|
列出所有当前加载的插件。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: 当前加载的插件名称列表。
|
List[str]: 当前加载的插件名称列表。
|
||||||
"""
|
"""
|
||||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||||
|
|
||||||
@@ -16,17 +18,38 @@ def list_registered_plugins() -> List[str]:
|
|||||||
列出所有已注册的插件。
|
列出所有已注册的插件。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: 已注册的插件名称列表。
|
List[str]: 已注册的插件名称列表。
|
||||||
"""
|
"""
|
||||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||||
|
|
||||||
return plugin_manager.list_registered_plugins()
|
return plugin_manager.list_registered_plugins()
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin_path(plugin_name: str) -> str:
|
||||||
|
"""
|
||||||
|
获取指定插件的路径。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_name (str): 插件名称。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 插件目录的绝对路径。
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 如果插件不存在。
|
||||||
|
"""
|
||||||
|
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||||
|
|
||||||
|
if plugin_path := plugin_manager.get_plugin_path(plugin_name):
|
||||||
|
return plugin_path
|
||||||
|
else:
|
||||||
|
raise ValueError(f"插件 '{plugin_name}' 不存在。")
|
||||||
|
|
||||||
|
|
||||||
async def remove_plugin(plugin_name: str) -> bool:
|
async def remove_plugin(plugin_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
卸载指定的插件。
|
卸载指定的插件。
|
||||||
|
|
||||||
**此函数是异步的,确保在异步环境中调用。**
|
**此函数是异步的,确保在异步环境中调用。**
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -43,7 +66,7 @@ async def remove_plugin(plugin_name: str) -> bool:
|
|||||||
async def reload_plugin(plugin_name: str) -> bool:
|
async def reload_plugin(plugin_name: str) -> bool:
|
||||||
"""
|
"""
|
||||||
重新加载指定的插件。
|
重新加载指定的插件。
|
||||||
|
|
||||||
**此函数是异步的,确保在异步环境中调用。**
|
**此函数是异步的,确保在异步环境中调用。**
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -71,6 +94,7 @@ def load_plugin(plugin_name: str) -> Tuple[bool, int]:
|
|||||||
|
|
||||||
return plugin_manager.load_registered_plugin_classes(plugin_name)
|
return plugin_manager.load_registered_plugin_classes(plugin_name)
|
||||||
|
|
||||||
|
|
||||||
def add_plugin_directory(plugin_directory: str) -> bool:
|
def add_plugin_directory(plugin_directory: str) -> bool:
|
||||||
"""
|
"""
|
||||||
添加插件目录。
|
添加插件目录。
|
||||||
@@ -84,6 +108,7 @@ def add_plugin_directory(plugin_directory: str) -> bool:
|
|||||||
|
|
||||||
return plugin_manager.add_plugin_directory(plugin_directory)
|
return plugin_manager.add_plugin_directory(plugin_directory)
|
||||||
|
|
||||||
|
|
||||||
def rescan_plugin_directory() -> Tuple[int, int]:
|
def rescan_plugin_directory() -> Tuple[int, int]:
|
||||||
"""
|
"""
|
||||||
重新扫描插件目录,加载新插件。
|
重新扫描插件目录,加载新插件。
|
||||||
@@ -92,4 +117,4 @@ def rescan_plugin_directory() -> Tuple[int, int]:
|
|||||||
"""
|
"""
|
||||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||||
|
|
||||||
return plugin_manager.rescan_plugin_directory()
|
return plugin_manager.rescan_plugin_directory()
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
import difflib
|
import difflib
|
||||||
import re
|
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ from src.common.logger import get_logger
|
|||||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||||
from src.chat.message_receive.uni_message_sender import HeartFCSender
|
from src.chat.message_receive.uni_message_sender import HeartFCSender
|
||||||
from src.chat.message_receive.message import MessageSending, MessageRecv
|
from src.chat.message_receive.message import MessageSending, MessageRecv
|
||||||
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat
|
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, replace_user_references_async
|
||||||
from src.person_info.person_info import get_person_info_manager
|
from src.person_info.person_info import get_person_info_manager
|
||||||
from maim_message import Seg, UserInfo
|
from maim_message import Seg, UserInfo
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
@@ -50,7 +49,7 @@ async def _send_to_target(
|
|||||||
display_message: str = "",
|
display_message: str = "",
|
||||||
typing: bool = False,
|
typing: bool = False,
|
||||||
reply_to: str = "",
|
reply_to: str = "",
|
||||||
reply_to_platform_id: str = "",
|
reply_to_platform_id: Optional[str] = None,
|
||||||
storage_message: bool = True,
|
storage_message: bool = True,
|
||||||
show_log: bool = True,
|
show_log: bool = True,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -61,8 +60,11 @@ async def _send_to_target(
|
|||||||
content: 消息内容
|
content: 消息内容
|
||||||
stream_id: 目标流ID
|
stream_id: 目标流ID
|
||||||
display_message: 显示消息
|
display_message: 显示消息
|
||||||
typing: 是否显示正在输入
|
typing: 是否模拟打字等待。
|
||||||
reply_to: 回复消息的格式,如"发送者:消息内容"
|
reply_to: 回复消息,格式为"发送者:消息内容"
|
||||||
|
reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!)
|
||||||
|
storage_message: 是否存储消息到数据库
|
||||||
|
show_log: 发送是否显示日志
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 是否发送成功
|
bool: 是否发送成功
|
||||||
@@ -98,6 +100,10 @@ async def _send_to_target(
|
|||||||
anchor_message = None
|
anchor_message = None
|
||||||
if reply_to:
|
if reply_to:
|
||||||
anchor_message = await _find_reply_message(target_stream, reply_to)
|
anchor_message = await _find_reply_message(target_stream, reply_to)
|
||||||
|
if anchor_message and anchor_message.message_info.user_info and not reply_to_platform_id:
|
||||||
|
reply_to_platform_id = (
|
||||||
|
f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}"
|
||||||
|
)
|
||||||
|
|
||||||
# 构建发送消息对象
|
# 构建发送消息对象
|
||||||
bot_message = MessageSending(
|
bot_message = MessageSending(
|
||||||
@@ -183,32 +189,8 @@ async def _find_reply_message(target_stream, reply_to: str) -> Optional[MessageR
|
|||||||
if person_name == sender:
|
if person_name == sender:
|
||||||
translate_text = message["processed_plain_text"]
|
translate_text = message["processed_plain_text"]
|
||||||
|
|
||||||
# 检查是否有 回复<aaa:bbb> 字段
|
# 使用独立函数处理用户引用格式
|
||||||
reply_pattern = r"回复<([^:<>]+):([^:<>]+)>"
|
translate_text = await replace_user_references_async(translate_text, platform)
|
||||||
if match := re.search(reply_pattern, translate_text):
|
|
||||||
aaa = match.group(1)
|
|
||||||
bbb = match.group(2)
|
|
||||||
reply_person_id = get_person_info_manager().get_person_id(platform, bbb)
|
|
||||||
reply_person_name = await get_person_info_manager().get_value(reply_person_id, "person_name") or aaa
|
|
||||||
# 在内容前加上回复信息
|
|
||||||
translate_text = re.sub(reply_pattern, f"回复 {reply_person_name}", translate_text, count=1)
|
|
||||||
|
|
||||||
# 检查是否有 @<aaa:bbb> 字段
|
|
||||||
at_pattern = r"@<([^:<>]+):([^:<>]+)>"
|
|
||||||
at_matches = list(re.finditer(at_pattern, translate_text))
|
|
||||||
if at_matches:
|
|
||||||
new_content = ""
|
|
||||||
last_end = 0
|
|
||||||
for m in at_matches:
|
|
||||||
new_content += translate_text[last_end : m.start()]
|
|
||||||
aaa = m.group(1)
|
|
||||||
bbb = m.group(2)
|
|
||||||
at_person_id = get_person_info_manager().get_person_id(platform, bbb)
|
|
||||||
at_person_name = await get_person_info_manager().get_value(at_person_id, "person_name") or aaa
|
|
||||||
new_content += f"@{at_person_name}"
|
|
||||||
last_end = m.end()
|
|
||||||
new_content += translate_text[last_end:]
|
|
||||||
translate_text = new_content
|
|
||||||
|
|
||||||
similarity = difflib.SequenceMatcher(None, text, translate_text).ratio()
|
similarity = difflib.SequenceMatcher(None, text, translate_text).ratio()
|
||||||
if similarity >= 0.9:
|
if similarity >= 0.9:
|
||||||
@@ -287,12 +269,22 @@ async def text_to_stream(
|
|||||||
stream_id: 聊天流ID
|
stream_id: 聊天流ID
|
||||||
typing: 是否显示正在输入
|
typing: 是否显示正在输入
|
||||||
reply_to: 回复消息,格式为"发送者:消息内容"
|
reply_to: 回复消息,格式为"发送者:消息内容"
|
||||||
|
reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!)
|
||||||
storage_message: 是否存储消息到数据库
|
storage_message: 是否存储消息到数据库
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 是否发送成功
|
bool: 是否发送成功
|
||||||
"""
|
"""
|
||||||
return await _send_to_target("text", text, stream_id, "", typing, reply_to, reply_to_platform_id, storage_message)
|
return await _send_to_target(
|
||||||
|
"text",
|
||||||
|
text,
|
||||||
|
stream_id,
|
||||||
|
"",
|
||||||
|
typing,
|
||||||
|
reply_to,
|
||||||
|
reply_to_platform_id=reply_to_platform_id,
|
||||||
|
storage_message=storage_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool:
|
async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool:
|
||||||
@@ -375,249 +367,3 @@ async def custom_to_stream(
|
|||||||
storage_message=storage_message,
|
storage_message=storage_message,
|
||||||
show_log=show_log,
|
show_log=show_log,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def text_to_group(
|
|
||||||
text: str,
|
|
||||||
group_id: str,
|
|
||||||
platform: str = "qq",
|
|
||||||
typing: bool = False,
|
|
||||||
reply_to: str = "",
|
|
||||||
storage_message: bool = True,
|
|
||||||
) -> bool:
|
|
||||||
"""向群聊发送文本消息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text: 要发送的文本内容
|
|
||||||
group_id: 群聊ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
typing: 是否显示正在输入
|
|
||||||
reply_to: 回复消息,格式为"发送者:消息内容"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, group_id, True)
|
|
||||||
|
|
||||||
return await _send_to_target("text", text, stream_id, "", typing, reply_to, storage_message=storage_message)
|
|
||||||
|
|
||||||
|
|
||||||
async def text_to_user(
|
|
||||||
text: str,
|
|
||||||
user_id: str,
|
|
||||||
platform: str = "qq",
|
|
||||||
typing: bool = False,
|
|
||||||
reply_to: str = "",
|
|
||||||
storage_message: bool = True,
|
|
||||||
) -> bool:
|
|
||||||
"""向用户发送私聊文本消息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text: 要发送的文本内容
|
|
||||||
user_id: 用户ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
typing: 是否显示正在输入
|
|
||||||
reply_to: 回复消息,格式为"发送者:消息内容"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, user_id, False)
|
|
||||||
return await _send_to_target("text", text, stream_id, "", typing, reply_to, storage_message=storage_message)
|
|
||||||
|
|
||||||
|
|
||||||
async def emoji_to_group(emoji_base64: str, group_id: str, platform: str = "qq", storage_message: bool = True) -> bool:
|
|
||||||
"""向群聊发送表情包
|
|
||||||
|
|
||||||
Args:
|
|
||||||
emoji_base64: 表情包的base64编码
|
|
||||||
group_id: 群聊ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, group_id, True)
|
|
||||||
return await _send_to_target("emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message)
|
|
||||||
|
|
||||||
|
|
||||||
async def emoji_to_user(emoji_base64: str, user_id: str, platform: str = "qq", storage_message: bool = True) -> bool:
|
|
||||||
"""向用户发送表情包
|
|
||||||
|
|
||||||
Args:
|
|
||||||
emoji_base64: 表情包的base64编码
|
|
||||||
user_id: 用户ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, user_id, False)
|
|
||||||
return await _send_to_target("emoji", emoji_base64, stream_id, "", typing=False, storage_message=storage_message)
|
|
||||||
|
|
||||||
|
|
||||||
async def image_to_group(image_base64: str, group_id: str, platform: str = "qq", storage_message: bool = True) -> bool:
|
|
||||||
"""向群聊发送图片
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image_base64: 图片的base64编码
|
|
||||||
group_id: 群聊ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, group_id, True)
|
|
||||||
return await _send_to_target("image", image_base64, stream_id, "", typing=False, storage_message=storage_message)
|
|
||||||
|
|
||||||
|
|
||||||
async def image_to_user(image_base64: str, user_id: str, platform: str = "qq", storage_message: bool = True) -> bool:
|
|
||||||
"""向用户发送图片
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image_base64: 图片的base64编码
|
|
||||||
user_id: 用户ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, user_id, False)
|
|
||||||
return await _send_to_target("image", image_base64, stream_id, "", typing=False)
|
|
||||||
|
|
||||||
|
|
||||||
async def command_to_group(command: str, group_id: str, platform: str = "qq", storage_message: bool = True) -> bool:
|
|
||||||
"""向群聊发送命令
|
|
||||||
|
|
||||||
Args:
|
|
||||||
command: 命令
|
|
||||||
group_id: 群聊ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, group_id, True)
|
|
||||||
return await _send_to_target("command", command, stream_id, "", typing=False, storage_message=storage_message)
|
|
||||||
|
|
||||||
|
|
||||||
async def command_to_user(command: str, user_id: str, platform: str = "qq", storage_message: bool = True) -> bool:
|
|
||||||
"""向用户发送命令
|
|
||||||
|
|
||||||
Args:
|
|
||||||
command: 命令
|
|
||||||
user_id: 用户ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, user_id, False)
|
|
||||||
return await _send_to_target("command", command, stream_id, "", typing=False, storage_message=storage_message)
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# 通用发送函数 - 支持任意消息类型
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
async def custom_to_group(
|
|
||||||
message_type: str,
|
|
||||||
content: str,
|
|
||||||
group_id: str,
|
|
||||||
platform: str = "qq",
|
|
||||||
display_message: str = "",
|
|
||||||
typing: bool = False,
|
|
||||||
reply_to: str = "",
|
|
||||||
storage_message: bool = True,
|
|
||||||
) -> bool:
|
|
||||||
"""向群聊发送自定义类型消息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
message_type: 消息类型,如"text"、"image"、"emoji"、"video"、"file"等
|
|
||||||
content: 消息内容(通常是base64编码或文本)
|
|
||||||
group_id: 群聊ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
display_message: 显示消息
|
|
||||||
typing: 是否显示正在输入
|
|
||||||
reply_to: 回复消息,格式为"发送者:消息内容"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, group_id, True)
|
|
||||||
return await _send_to_target(
|
|
||||||
message_type, content, stream_id, display_message, typing, reply_to, storage_message=storage_message
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def custom_to_user(
|
|
||||||
message_type: str,
|
|
||||||
content: str,
|
|
||||||
user_id: str,
|
|
||||||
platform: str = "qq",
|
|
||||||
display_message: str = "",
|
|
||||||
typing: bool = False,
|
|
||||||
reply_to: str = "",
|
|
||||||
storage_message: bool = True,
|
|
||||||
) -> bool:
|
|
||||||
"""向用户发送自定义类型消息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
message_type: 消息类型,如"text"、"image"、"emoji"、"video"、"file"等
|
|
||||||
content: 消息内容(通常是base64编码或文本)
|
|
||||||
user_id: 用户ID
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
display_message: 显示消息
|
|
||||||
typing: 是否显示正在输入
|
|
||||||
reply_to: 回复消息,格式为"发送者:消息内容"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, user_id, False)
|
|
||||||
return await _send_to_target(
|
|
||||||
message_type, content, stream_id, display_message, typing, reply_to, storage_message=storage_message
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def custom_message(
|
|
||||||
message_type: str,
|
|
||||||
content: str,
|
|
||||||
target_id: str,
|
|
||||||
is_group: bool = True,
|
|
||||||
platform: str = "qq",
|
|
||||||
display_message: str = "",
|
|
||||||
typing: bool = False,
|
|
||||||
reply_to: str = "",
|
|
||||||
storage_message: bool = True,
|
|
||||||
) -> bool:
|
|
||||||
"""发送自定义消息的通用接口
|
|
||||||
|
|
||||||
Args:
|
|
||||||
message_type: 消息类型,如"text"、"image"、"emoji"、"video"、"file"、"audio"等
|
|
||||||
content: 消息内容
|
|
||||||
target_id: 目标ID(群ID或用户ID)
|
|
||||||
is_group: 是否为群聊,True为群聊,False为私聊
|
|
||||||
platform: 平台,默认为"qq"
|
|
||||||
display_message: 显示消息
|
|
||||||
typing: 是否显示正在输入
|
|
||||||
reply_to: 回复消息,格式为"发送者:消息内容"
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否发送成功
|
|
||||||
|
|
||||||
示例:
|
|
||||||
# 发送视频到群聊
|
|
||||||
await send_api.custom_message("video", video_base64, "123456", True)
|
|
||||||
|
|
||||||
# 发送文件到用户
|
|
||||||
await send_api.custom_message("file", file_base64, "987654", False)
|
|
||||||
|
|
||||||
# 发送音频到群聊并回复特定消息
|
|
||||||
await send_api.custom_message("audio", audio_base64, "123456", True, reply_to="张三:你好")
|
|
||||||
"""
|
|
||||||
stream_id = get_chat_manager().get_stream_id(platform, target_id, is_group)
|
|
||||||
return await _send_to_target(
|
|
||||||
message_type, content, stream_id, display_message, typing, reply_to, storage_message=storage_message
|
|
||||||
)
|
|
||||||
|
|||||||
27
src/plugin_system/apis/tool_api.py
Normal file
27
src/plugin_system/apis/tool_api.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from typing import Optional, Type
|
||||||
|
from src.plugin_system.base.base_tool import BaseTool
|
||||||
|
from src.plugin_system.base.component_types import ComponentType
|
||||||
|
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("tool_api")
|
||||||
|
|
||||||
|
|
||||||
|
def get_tool_instance(tool_name: str) -> Optional[BaseTool]:
|
||||||
|
"""获取公开工具实例"""
|
||||||
|
from src.plugin_system.core import component_registry
|
||||||
|
|
||||||
|
tool_class: Type[BaseTool] = component_registry.get_component_class(tool_name, ComponentType.TOOL) # type: ignore
|
||||||
|
return tool_class() if tool_class else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_llm_available_tool_definitions():
|
||||||
|
"""获取LLM可用的工具定义列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[str, Dict[str, Any]]]: 工具定义列表,为[("tool_name", 定义)]
|
||||||
|
"""
|
||||||
|
from src.plugin_system.core import component_registry
|
||||||
|
|
||||||
|
llm_available_tools = component_registry.get_llm_available_tools()
|
||||||
|
return [(name, tool_class.get_tool_definition()) for name, tool_class in llm_available_tools.items()]
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
"""工具类API模块
|
|
||||||
|
|
||||||
提供了各种辅助功能
|
|
||||||
使用方式:
|
|
||||||
from src.plugin_system.apis import utils_api
|
|
||||||
plugin_path = utils_api.get_plugin_path()
|
|
||||||
data = utils_api.read_json_file("data.json")
|
|
||||||
timestamp = utils_api.get_timestamp()
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import inspect
|
|
||||||
import datetime
|
|
||||||
import uuid
|
|
||||||
from typing import Any, Optional
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
|
|
||||||
logger = get_logger("utils_api")
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# 文件操作API函数
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_path(caller_frame=None) -> str:
|
|
||||||
"""获取调用者插件的路径
|
|
||||||
|
|
||||||
Args:
|
|
||||||
caller_frame: 调用者的栈帧,默认为None(自动获取)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: 插件目录的绝对路径
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if caller_frame is None:
|
|
||||||
caller_frame = inspect.currentframe().f_back # type: ignore
|
|
||||||
|
|
||||||
plugin_module_path = inspect.getfile(caller_frame) # type: ignore
|
|
||||||
plugin_dir = os.path.dirname(plugin_module_path)
|
|
||||||
return plugin_dir
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[UtilsAPI] 获取插件路径失败: {e}")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def read_json_file(file_path: str, default: Any = None) -> Any:
|
|
||||||
"""读取JSON文件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
file_path: 文件路径,可以是相对于插件目录的路径
|
|
||||||
default: 如果文件不存在或读取失败时返回的默认值
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Any: JSON数据或默认值
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 如果是相对路径,则相对于调用者的插件目录
|
|
||||||
if not os.path.isabs(file_path):
|
|
||||||
caller_frame = inspect.currentframe().f_back # type: ignore
|
|
||||||
plugin_dir = get_plugin_path(caller_frame)
|
|
||||||
file_path = os.path.join(plugin_dir, file_path)
|
|
||||||
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
logger.warning(f"[UtilsAPI] 文件不存在: {file_path}")
|
|
||||||
return default
|
|
||||||
|
|
||||||
with open(file_path, "r", encoding="utf-8") as f:
|
|
||||||
return json.load(f)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[UtilsAPI] 读取JSON文件出错: {e}")
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
|
||||||
def write_json_file(file_path: str, data: Any, indent: int = 2) -> bool:
|
|
||||||
"""写入JSON文件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
file_path: 文件路径,可以是相对于插件目录的路径
|
|
||||||
data: 要写入的数据
|
|
||||||
indent: JSON缩进
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 是否写入成功
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 如果是相对路径,则相对于调用者的插件目录
|
|
||||||
if not os.path.isabs(file_path):
|
|
||||||
caller_frame = inspect.currentframe().f_back # type: ignore
|
|
||||||
plugin_dir = get_plugin_path(caller_frame)
|
|
||||||
file_path = os.path.join(plugin_dir, file_path)
|
|
||||||
|
|
||||||
# 确保目录存在
|
|
||||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
||||||
|
|
||||||
with open(file_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(data, f, ensure_ascii=False, indent=indent)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[UtilsAPI] 写入JSON文件出错: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# 时间相关API函数
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def get_timestamp() -> int:
|
|
||||||
"""获取当前时间戳
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: 当前时间戳(秒)
|
|
||||||
"""
|
|
||||||
return int(time.time())
|
|
||||||
|
|
||||||
|
|
||||||
def format_time(timestamp: Optional[int | float] = None, format_str: str = "%Y-%m-%d %H:%M:%S") -> str:
|
|
||||||
"""格式化时间
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timestamp: 时间戳,如果为None则使用当前时间
|
|
||||||
format_str: 时间格式字符串
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: 格式化后的时间字符串
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if timestamp is None:
|
|
||||||
timestamp = time.time()
|
|
||||||
return datetime.datetime.fromtimestamp(timestamp).strftime(format_str)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[UtilsAPI] 格式化时间失败: {e}")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def parse_time(time_str: str, format_str: str = "%Y-%m-%d %H:%M:%S") -> int:
|
|
||||||
"""解析时间字符串为时间戳
|
|
||||||
|
|
||||||
Args:
|
|
||||||
time_str: 时间字符串
|
|
||||||
format_str: 时间格式字符串
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: 时间戳(秒)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
dt = datetime.datetime.strptime(time_str, format_str)
|
|
||||||
return int(dt.timestamp())
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[UtilsAPI] 解析时间失败: {e}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# 其他工具函数
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def generate_unique_id() -> str:
|
|
||||||
"""生成唯一ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: 唯一ID
|
|
||||||
"""
|
|
||||||
return str(uuid.uuid4())
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
from .base_plugin import BasePlugin
|
from .base_plugin import BasePlugin
|
||||||
from .base_action import BaseAction
|
from .base_action import BaseAction
|
||||||
|
from .base_tool import BaseTool
|
||||||
from .base_command import BaseCommand
|
from .base_command import BaseCommand
|
||||||
from .base_events_handler import BaseEventHandler
|
from .base_events_handler import BaseEventHandler
|
||||||
from .component_types import (
|
from .component_types import (
|
||||||
@@ -15,6 +16,7 @@ from .component_types import (
|
|||||||
ComponentInfo,
|
ComponentInfo,
|
||||||
ActionInfo,
|
ActionInfo,
|
||||||
CommandInfo,
|
CommandInfo,
|
||||||
|
ToolInfo,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
PythonDependency,
|
PythonDependency,
|
||||||
EventHandlerInfo,
|
EventHandlerInfo,
|
||||||
@@ -27,12 +29,14 @@ __all__ = [
|
|||||||
"BasePlugin",
|
"BasePlugin",
|
||||||
"BaseAction",
|
"BaseAction",
|
||||||
"BaseCommand",
|
"BaseCommand",
|
||||||
|
"BaseTool",
|
||||||
"ComponentType",
|
"ComponentType",
|
||||||
"ActionActivationType",
|
"ActionActivationType",
|
||||||
"ChatMode",
|
"ChatMode",
|
||||||
"ComponentInfo",
|
"ComponentInfo",
|
||||||
"ActionInfo",
|
"ActionInfo",
|
||||||
"CommandInfo",
|
"CommandInfo",
|
||||||
|
"ToolInfo",
|
||||||
"PluginInfo",
|
"PluginInfo",
|
||||||
"PythonDependency",
|
"PythonDependency",
|
||||||
"ConfigField",
|
"ConfigField",
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ class BaseAction(ABC):
|
|||||||
return False, f"等待新消息失败: {str(e)}"
|
return False, f"等待新消息失败: {str(e)}"
|
||||||
|
|
||||||
async def send_text(
|
async def send_text(
|
||||||
self, content: str, reply_to: str = "", reply_to_platform_id: str = "", typing: bool = False
|
self, content: str, reply_to: str = "", typing: bool = False
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""发送文本消息
|
"""发送文本消息
|
||||||
|
|
||||||
@@ -227,7 +227,6 @@ class BaseAction(ABC):
|
|||||||
text=content,
|
text=content,
|
||||||
stream_id=self.chat_id,
|
stream_id=self.chat_id,
|
||||||
reply_to=reply_to,
|
reply_to=reply_to,
|
||||||
reply_to_platform_id=reply_to_platform_id,
|
|
||||||
typing=typing,
|
typing=typing,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -384,7 +383,7 @@ class BaseAction(ABC):
|
|||||||
keyword_case_sensitive=getattr(cls, "keyword_case_sensitive", False),
|
keyword_case_sensitive=getattr(cls, "keyword_case_sensitive", False),
|
||||||
mode_enable=getattr(cls, "mode_enable", ChatMode.ALL),
|
mode_enable=getattr(cls, "mode_enable", ChatMode.ALL),
|
||||||
parallel_action=getattr(cls, "parallel_action", True),
|
parallel_action=getattr(cls, "parallel_action", True),
|
||||||
random_activation_probability=getattr(cls, "random_activation_probability", 0.3),
|
random_activation_probability=getattr(cls, "random_activation_probability", 0.0),
|
||||||
llm_judge_prompt=getattr(cls, "llm_judge_prompt", ""),
|
llm_judge_prompt=getattr(cls, "llm_judge_prompt", ""),
|
||||||
# 使用正确的字段名
|
# 使用正确的字段名
|
||||||
action_parameters=getattr(cls, "action_parameters", {}).copy(),
|
action_parameters=getattr(cls, "action_parameters", {}).copy(),
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ class BaseCommand(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_config(self, key: str, default=None):
|
def get_config(self, key: str, default=None):
|
||||||
"""获取插件配置值,支持嵌套键访问
|
"""获取插件配置值,使用嵌套键访问
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: 配置键名,支持嵌套访问如 "section.subsection.key"
|
key: 配置键名,使用嵌套访问如 "section.subsection.key"
|
||||||
default: 默认值
|
default: 默认值
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
85
src/plugin_system/base/base_tool.py
Normal file
85
src/plugin_system/base/base_tool.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Any, Dict
|
||||||
|
from rich.traceback import install
|
||||||
|
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
from src.plugin_system.base.component_types import ComponentType, ToolInfo
|
||||||
|
|
||||||
|
install(extra_lines=3)
|
||||||
|
|
||||||
|
logger = get_logger("base_tool")
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTool(ABC):
|
||||||
|
"""所有工具的基类"""
|
||||||
|
|
||||||
|
name: str = ""
|
||||||
|
"""工具的名称"""
|
||||||
|
description: str = ""
|
||||||
|
"""工具的描述"""
|
||||||
|
parameters: Dict[str, Any] = {}
|
||||||
|
"""工具的参数定义"""
|
||||||
|
available_for_llm: bool = False
|
||||||
|
"""是否可供LLM使用"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_tool_definition(cls) -> dict[str, Any]:
|
||||||
|
"""获取工具定义,用于LLM工具调用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 工具定义字典
|
||||||
|
"""
|
||||||
|
if not cls.name or not cls.description or not cls.parameters:
|
||||||
|
raise NotImplementedError(f"工具类 {cls.__name__} 必须定义 name, description 和 parameters 属性")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"function": {"name": cls.name, "description": cls.description, "parameters": cls.parameters},
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_tool_info(cls) -> ToolInfo:
|
||||||
|
"""获取工具信息"""
|
||||||
|
if not cls.name or not cls.description or not cls.parameters:
|
||||||
|
raise NotImplementedError(f"工具类 {cls.__name__} 必须定义 name, description 和 parameters 属性")
|
||||||
|
|
||||||
|
return ToolInfo(
|
||||||
|
name=cls.name,
|
||||||
|
tool_description=cls.description,
|
||||||
|
enabled=cls.available_for_llm,
|
||||||
|
tool_parameters=cls.parameters,
|
||||||
|
component_type=ComponentType.TOOL,
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""执行工具函数(供llm调用)
|
||||||
|
通过该方法,maicore会通过llm的tool call来调用工具
|
||||||
|
传入的是json格式的参数,符合parameters定义的格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_args: 工具调用参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 工具执行结果
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("子类必须实现execute方法")
|
||||||
|
|
||||||
|
async def direct_execute(self, **function_args: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""直接执行工具函数(供插件调用)
|
||||||
|
通过该方法,插件可以直接调用工具,而不需要传入字典格式的参数
|
||||||
|
插件可以直接调用此方法,用更加明了的方式传入参数
|
||||||
|
示例: result = await tool.direct_execute(arg1="参数",arg2="参数2")
|
||||||
|
|
||||||
|
工具开发者可以重写此方法以实现与llm调用差异化的执行逻辑
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**function_args: 工具调用参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 工具执行结果
|
||||||
|
"""
|
||||||
|
if self.parameters and (missing := [p for p in self.parameters.get("required", []) if p not in function_args]):
|
||||||
|
raise ValueError(f"工具类 {self.__class__.__name__} 缺少必要参数: {', '.join(missing)}")
|
||||||
|
|
||||||
|
return await self.execute(function_args)
|
||||||
@@ -10,6 +10,7 @@ class ComponentType(Enum):
|
|||||||
|
|
||||||
ACTION = "action" # 动作组件
|
ACTION = "action" # 动作组件
|
||||||
COMMAND = "command" # 命令组件
|
COMMAND = "command" # 命令组件
|
||||||
|
TOOL = "tool" # 服务组件(预留)
|
||||||
SCHEDULER = "scheduler" # 定时任务组件(预留)
|
SCHEDULER = "scheduler" # 定时任务组件(预留)
|
||||||
EVENT_HANDLER = "event_handler" # 事件处理组件(预留)
|
EVENT_HANDLER = "event_handler" # 事件处理组件(预留)
|
||||||
|
|
||||||
@@ -144,7 +145,17 @@ class CommandInfo(ComponentInfo):
|
|||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
self.component_type = ComponentType.COMMAND
|
self.component_type = ComponentType.COMMAND
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ToolInfo(ComponentInfo):
|
||||||
|
"""工具组件信息"""
|
||||||
|
|
||||||
|
tool_parameters: Dict[str, Any] = field(default_factory=dict) # 工具参数定义
|
||||||
|
tool_description: str = "" # 工具描述
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
super().__post_init__()
|
||||||
|
self.component_type = ComponentType.TOOL
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EventHandlerInfo(ComponentInfo):
|
class EventHandlerInfo(ComponentInfo):
|
||||||
|
|||||||
@@ -6,14 +6,12 @@
|
|||||||
|
|
||||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
from src.plugin_system.core.dependency_manager import dependency_manager
|
|
||||||
from src.plugin_system.core.events_manager import events_manager
|
from src.plugin_system.core.events_manager import events_manager
|
||||||
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"plugin_manager",
|
"plugin_manager",
|
||||||
"component_registry",
|
"component_registry",
|
||||||
"dependency_manager",
|
|
||||||
"events_manager",
|
"events_manager",
|
||||||
"global_announcement_manager",
|
"global_announcement_manager",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from src.common.logger import get_logger
|
|||||||
from src.plugin_system.base.component_types import (
|
from src.plugin_system.base.component_types import (
|
||||||
ComponentInfo,
|
ComponentInfo,
|
||||||
ActionInfo,
|
ActionInfo,
|
||||||
|
ToolInfo,
|
||||||
CommandInfo,
|
CommandInfo,
|
||||||
EventHandlerInfo,
|
EventHandlerInfo,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
@@ -13,6 +14,7 @@ from src.plugin_system.base.component_types import (
|
|||||||
)
|
)
|
||||||
from src.plugin_system.base.base_command import BaseCommand
|
from src.plugin_system.base.base_command import BaseCommand
|
||||||
from src.plugin_system.base.base_action import BaseAction
|
from src.plugin_system.base.base_action import BaseAction
|
||||||
|
from src.plugin_system.base.base_tool import BaseTool
|
||||||
from src.plugin_system.base.base_events_handler import BaseEventHandler
|
from src.plugin_system.base.base_events_handler import BaseEventHandler
|
||||||
|
|
||||||
logger = get_logger("component_registry")
|
logger = get_logger("component_registry")
|
||||||
@@ -30,7 +32,7 @@ class ComponentRegistry:
|
|||||||
"""组件注册表 命名空间式组件名 -> 组件信息"""
|
"""组件注册表 命名空间式组件名 -> 组件信息"""
|
||||||
self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {types: {} for types in ComponentType}
|
self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {types: {} for types in ComponentType}
|
||||||
"""类型 -> 组件原名称 -> 组件信息"""
|
"""类型 -> 组件原名称 -> 组件信息"""
|
||||||
self._components_classes: Dict[str, Type[Union[BaseCommand, BaseAction, BaseEventHandler]]] = {}
|
self._components_classes: Dict[str, Type[Union[BaseCommand, BaseAction, BaseTool, BaseEventHandler]]] = {}
|
||||||
"""命名空间式组件名 -> 组件类"""
|
"""命名空间式组件名 -> 组件类"""
|
||||||
|
|
||||||
# 插件注册表
|
# 插件注册表
|
||||||
@@ -49,6 +51,10 @@ class ComponentRegistry:
|
|||||||
self._command_patterns: Dict[Pattern, str] = {}
|
self._command_patterns: Dict[Pattern, str] = {}
|
||||||
"""编译后的正则 -> command名"""
|
"""编译后的正则 -> command名"""
|
||||||
|
|
||||||
|
# 工具特定注册表
|
||||||
|
self._tool_registry: Dict[str, Type[BaseTool]] = {} # 工具名 -> 工具类
|
||||||
|
self._llm_available_tools: Dict[str, Type[BaseTool]] = {} # llm可用的工具名 -> 工具类
|
||||||
|
|
||||||
# EventHandler特定注册表
|
# EventHandler特定注册表
|
||||||
self._event_handler_registry: Dict[str, Type[BaseEventHandler]] = {}
|
self._event_handler_registry: Dict[str, Type[BaseEventHandler]] = {}
|
||||||
"""event_handler名 -> event_handler类"""
|
"""event_handler名 -> event_handler类"""
|
||||||
@@ -79,7 +85,9 @@ class ComponentRegistry:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def register_component(
|
def register_component(
|
||||||
self, component_info: ComponentInfo, component_class: Type[Union[BaseCommand, BaseAction, BaseEventHandler]]
|
self,
|
||||||
|
component_info: ComponentInfo,
|
||||||
|
component_class: Type[Union[BaseCommand, BaseAction, BaseEventHandler, BaseTool]],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""注册组件
|
"""注册组件
|
||||||
|
|
||||||
@@ -125,6 +133,10 @@ class ComponentRegistry:
|
|||||||
assert isinstance(component_info, CommandInfo)
|
assert isinstance(component_info, CommandInfo)
|
||||||
assert issubclass(component_class, BaseCommand)
|
assert issubclass(component_class, BaseCommand)
|
||||||
ret = self._register_command_component(component_info, component_class)
|
ret = self._register_command_component(component_info, component_class)
|
||||||
|
case ComponentType.TOOL:
|
||||||
|
assert isinstance(component_info, ToolInfo)
|
||||||
|
assert issubclass(component_class, BaseTool)
|
||||||
|
ret = self._register_tool_component(component_info, component_class)
|
||||||
case ComponentType.EVENT_HANDLER:
|
case ComponentType.EVENT_HANDLER:
|
||||||
assert isinstance(component_info, EventHandlerInfo)
|
assert isinstance(component_info, EventHandlerInfo)
|
||||||
assert issubclass(component_class, BaseEventHandler)
|
assert issubclass(component_class, BaseEventHandler)
|
||||||
@@ -180,6 +192,18 @@ class ComponentRegistry:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _register_tool_component(self, tool_info: ToolInfo, tool_class: Type[BaseTool]) -> bool:
|
||||||
|
"""注册Tool组件到Tool特定注册表"""
|
||||||
|
tool_name = tool_info.name
|
||||||
|
|
||||||
|
self._tool_registry[tool_name] = tool_class
|
||||||
|
|
||||||
|
# 如果是llm可用的且启用的工具,添加到 llm可用工具列表
|
||||||
|
if tool_info.enabled:
|
||||||
|
self._llm_available_tools[tool_name] = tool_class
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _register_event_handler_component(
|
def _register_event_handler_component(
|
||||||
self, handler_info: EventHandlerInfo, handler_class: Type[BaseEventHandler]
|
self, handler_info: EventHandlerInfo, handler_class: Type[BaseEventHandler]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -222,6 +246,9 @@ class ComponentRegistry:
|
|||||||
keys_to_remove = [k for k, v in self._command_patterns.items() if v == component_name]
|
keys_to_remove = [k for k, v in self._command_patterns.items() if v == component_name]
|
||||||
for key in keys_to_remove:
|
for key in keys_to_remove:
|
||||||
self._command_patterns.pop(key)
|
self._command_patterns.pop(key)
|
||||||
|
case ComponentType.TOOL:
|
||||||
|
self._tool_registry.pop(component_name)
|
||||||
|
self._llm_available_tools.pop(component_name)
|
||||||
case ComponentType.EVENT_HANDLER:
|
case ComponentType.EVENT_HANDLER:
|
||||||
from .events_manager import events_manager # 延迟导入防止循环导入问题
|
from .events_manager import events_manager # 延迟导入防止循环导入问题
|
||||||
|
|
||||||
@@ -234,13 +261,13 @@ class ComponentRegistry:
|
|||||||
self._components_classes.pop(namespaced_name)
|
self._components_classes.pop(namespaced_name)
|
||||||
logger.info(f"组件 {component_name} 已移除")
|
logger.info(f"组件 {component_name} 已移除")
|
||||||
return True
|
return True
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
logger.warning(f"移除组件时未找到组件: {component_name}")
|
logger.warning(f"移除组件时未找到组件: {component_name}, 发生错误: {e}")
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"移除组件 {component_name} 时发生错误: {e}")
|
logger.error(f"移除组件 {component_name} 时发生错误: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def remove_plugin_registry(self, plugin_name: str) -> bool:
|
def remove_plugin_registry(self, plugin_name: str) -> bool:
|
||||||
"""移除插件注册信息
|
"""移除插件注册信息
|
||||||
|
|
||||||
@@ -281,6 +308,10 @@ class ComponentRegistry:
|
|||||||
assert isinstance(target_component_info, CommandInfo)
|
assert isinstance(target_component_info, CommandInfo)
|
||||||
pattern = target_component_info.command_pattern
|
pattern = target_component_info.command_pattern
|
||||||
self._command_patterns[re.compile(pattern)] = component_name
|
self._command_patterns[re.compile(pattern)] = component_name
|
||||||
|
case ComponentType.TOOL:
|
||||||
|
assert isinstance(target_component_info, ToolInfo)
|
||||||
|
assert issubclass(target_component_class, BaseTool)
|
||||||
|
self._llm_available_tools[component_name] = target_component_class
|
||||||
case ComponentType.EVENT_HANDLER:
|
case ComponentType.EVENT_HANDLER:
|
||||||
assert isinstance(target_component_info, EventHandlerInfo)
|
assert isinstance(target_component_info, EventHandlerInfo)
|
||||||
assert issubclass(target_component_class, BaseEventHandler)
|
assert issubclass(target_component_class, BaseEventHandler)
|
||||||
@@ -308,20 +339,29 @@ class ComponentRegistry:
|
|||||||
logger.warning(f"组件 {component_name} 未注册,无法禁用")
|
logger.warning(f"组件 {component_name} 未注册,无法禁用")
|
||||||
return False
|
return False
|
||||||
target_component_info.enabled = False
|
target_component_info.enabled = False
|
||||||
match component_type:
|
try:
|
||||||
case ComponentType.ACTION:
|
match component_type:
|
||||||
self._default_actions.pop(component_name, None)
|
case ComponentType.ACTION:
|
||||||
case ComponentType.COMMAND:
|
self._default_actions.pop(component_name)
|
||||||
self._command_patterns = {k: v for k, v in self._command_patterns.items() if v != component_name}
|
case ComponentType.COMMAND:
|
||||||
case ComponentType.EVENT_HANDLER:
|
self._command_patterns = {k: v for k, v in self._command_patterns.items() if v != component_name}
|
||||||
self._enabled_event_handlers.pop(component_name, None)
|
case ComponentType.TOOL:
|
||||||
from .events_manager import events_manager # 延迟导入防止循环导入问题
|
self._llm_available_tools.pop(component_name)
|
||||||
|
case ComponentType.EVENT_HANDLER:
|
||||||
|
self._enabled_event_handlers.pop(component_name)
|
||||||
|
from .events_manager import events_manager # 延迟导入防止循环导入问题
|
||||||
|
|
||||||
await events_manager.unregister_event_subscriber(component_name)
|
await events_manager.unregister_event_subscriber(component_name)
|
||||||
self._components[component_name].enabled = False
|
self._components[component_name].enabled = False
|
||||||
self._components_by_type[component_type][component_name].enabled = False
|
self._components_by_type[component_type][component_name].enabled = False
|
||||||
logger.info(f"组件 {component_name} 已禁用")
|
logger.info(f"组件 {component_name} 已禁用")
|
||||||
return True
|
return True
|
||||||
|
except KeyError as e:
|
||||||
|
logger.warning(f"禁用组件时未找到组件或已禁用: {component_name}, 发生错误: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"禁用组件 {component_name} 时发生错误: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
# === 组件查询方法 ===
|
# === 组件查询方法 ===
|
||||||
def get_component_info(
|
def get_component_info(
|
||||||
@@ -371,7 +411,7 @@ class ComponentRegistry:
|
|||||||
self,
|
self,
|
||||||
component_name: str,
|
component_name: str,
|
||||||
component_type: Optional[ComponentType] = None,
|
component_type: Optional[ComponentType] = None,
|
||||||
) -> Optional[Union[Type[BaseCommand], Type[BaseAction], Type[BaseEventHandler]]]:
|
) -> Optional[Union[Type[BaseCommand], Type[BaseAction], Type[BaseEventHandler], Type[BaseTool]]]:
|
||||||
"""获取组件类,支持自动命名空间解析
|
"""获取组件类,支持自动命名空间解析
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -476,6 +516,27 @@ class ComponentRegistry:
|
|||||||
command_info,
|
command_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# === Tool 特定查询方法 ===
|
||||||
|
def get_tool_registry(self) -> Dict[str, Type[BaseTool]]:
|
||||||
|
"""获取Tool注册表"""
|
||||||
|
return self._tool_registry.copy()
|
||||||
|
|
||||||
|
def get_llm_available_tools(self) -> Dict[str, Type[BaseTool]]:
|
||||||
|
"""获取LLM可用的Tool列表"""
|
||||||
|
return self._llm_available_tools.copy()
|
||||||
|
|
||||||
|
def get_registered_tool_info(self, tool_name: str) -> Optional[ToolInfo]:
|
||||||
|
"""获取Tool信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_name: 工具名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ToolInfo: 工具信息对象,如果工具不存在则返回 None
|
||||||
|
"""
|
||||||
|
info = self.get_component_info(tool_name, ComponentType.TOOL)
|
||||||
|
return info if isinstance(info, ToolInfo) else None
|
||||||
|
|
||||||
# === EventHandler 特定查询方法 ===
|
# === EventHandler 特定查询方法 ===
|
||||||
|
|
||||||
def get_event_handler_registry(self) -> Dict[str, Type[BaseEventHandler]]:
|
def get_event_handler_registry(self) -> Dict[str, Type[BaseEventHandler]]:
|
||||||
@@ -529,17 +590,21 @@ class ComponentRegistry:
|
|||||||
"""获取注册中心统计信息"""
|
"""获取注册中心统计信息"""
|
||||||
action_components: int = 0
|
action_components: int = 0
|
||||||
command_components: int = 0
|
command_components: int = 0
|
||||||
|
tool_components: int = 0
|
||||||
events_handlers: int = 0
|
events_handlers: int = 0
|
||||||
for component in self._components.values():
|
for component in self._components.values():
|
||||||
if component.component_type == ComponentType.ACTION:
|
if component.component_type == ComponentType.ACTION:
|
||||||
action_components += 1
|
action_components += 1
|
||||||
elif component.component_type == ComponentType.COMMAND:
|
elif component.component_type == ComponentType.COMMAND:
|
||||||
command_components += 1
|
command_components += 1
|
||||||
|
elif component.component_type == ComponentType.TOOL:
|
||||||
|
tool_components += 1
|
||||||
elif component.component_type == ComponentType.EVENT_HANDLER:
|
elif component.component_type == ComponentType.EVENT_HANDLER:
|
||||||
events_handlers += 1
|
events_handlers += 1
|
||||||
return {
|
return {
|
||||||
"action_components": action_components,
|
"action_components": action_components,
|
||||||
"command_components": command_components,
|
"command_components": command_components,
|
||||||
|
"tool_components": tool_components,
|
||||||
"event_handlers": events_handlers,
|
"event_handlers": events_handlers,
|
||||||
"total_components": len(self._components),
|
"total_components": len(self._components),
|
||||||
"total_plugins": len(self._plugins),
|
"total_plugins": len(self._plugins),
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
"""
|
|
||||||
插件依赖管理器
|
|
||||||
|
|
||||||
负责检查和安装插件的Python包依赖
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import importlib
|
|
||||||
from typing import List, Dict, Tuple, Any
|
|
||||||
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
from src.plugin_system.base.component_types import PythonDependency
|
|
||||||
|
|
||||||
logger = get_logger("dependency_manager")
|
|
||||||
|
|
||||||
|
|
||||||
class DependencyManager:
|
|
||||||
"""依赖管理器"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.install_log: List[str] = []
|
|
||||||
self.failed_installs: Dict[str, str] = {}
|
|
||||||
|
|
||||||
def check_dependencies(
|
|
||||||
self, dependencies: List[PythonDependency]
|
|
||||||
) -> Tuple[List[PythonDependency], List[PythonDependency]]:
|
|
||||||
"""检查依赖包状态
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dependencies: 依赖包列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Tuple[List[PythonDependency], List[PythonDependency]]: (缺失的依赖, 可选缺失的依赖)
|
|
||||||
"""
|
|
||||||
missing_required = []
|
|
||||||
missing_optional = []
|
|
||||||
|
|
||||||
for dep in dependencies:
|
|
||||||
if self._is_package_available(dep.package_name):
|
|
||||||
logger.debug(f"依赖包已存在: {dep.package_name}")
|
|
||||||
elif dep.optional:
|
|
||||||
missing_optional.append(dep)
|
|
||||||
logger.warning(f"可选依赖包缺失: {dep.package_name} - {dep.description}")
|
|
||||||
else:
|
|
||||||
missing_required.append(dep)
|
|
||||||
logger.error(f"必需依赖包缺失: {dep.package_name} - {dep.description}")
|
|
||||||
return missing_required, missing_optional
|
|
||||||
|
|
||||||
def _is_package_available(self, package_name: str) -> bool:
|
|
||||||
"""检查包是否可用"""
|
|
||||||
try:
|
|
||||||
importlib.import_module(package_name)
|
|
||||||
return True
|
|
||||||
except ImportError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def install_dependencies(self, dependencies: List[PythonDependency], auto_install: bool = False) -> bool:
|
|
||||||
"""安装依赖包
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dependencies: 需要安装的依赖包列表
|
|
||||||
auto_install: 是否自动安装(True时不询问用户)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 安装是否成功
|
|
||||||
"""
|
|
||||||
if not dependencies:
|
|
||||||
return True
|
|
||||||
|
|
||||||
logger.info(f"需要安装 {len(dependencies)} 个依赖包")
|
|
||||||
|
|
||||||
# 显示将要安装的包
|
|
||||||
for dep in dependencies:
|
|
||||||
install_cmd = dep.get_pip_requirement()
|
|
||||||
logger.info(f" - {install_cmd} {'(可选)' if dep.optional else '(必需)'}")
|
|
||||||
if dep.description:
|
|
||||||
logger.info(f" 说明: {dep.description}")
|
|
||||||
|
|
||||||
if not auto_install:
|
|
||||||
# 这里可以添加用户确认逻辑
|
|
||||||
logger.warning("手动安装模式:请手动运行 pip install 命令安装依赖包")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 执行安装
|
|
||||||
success_count = 0
|
|
||||||
for dep in dependencies:
|
|
||||||
if self._install_single_package(dep):
|
|
||||||
success_count += 1
|
|
||||||
else:
|
|
||||||
self.failed_installs[dep.package_name] = f"安装失败: {dep.get_pip_requirement()}"
|
|
||||||
|
|
||||||
logger.info(f"依赖安装完成: {success_count}/{len(dependencies)} 个成功")
|
|
||||||
return success_count == len(dependencies)
|
|
||||||
|
|
||||||
def _install_single_package(self, dependency: PythonDependency) -> bool:
|
|
||||||
"""安装单个包"""
|
|
||||||
pip_requirement = dependency.get_pip_requirement()
|
|
||||||
|
|
||||||
try:
|
|
||||||
logger.info(f"正在安装: {pip_requirement}")
|
|
||||||
|
|
||||||
# 使用subprocess安装包
|
|
||||||
cmd = [sys.executable, "-m", "pip", "install", pip_requirement]
|
|
||||||
result = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=300, # 5分钟超时
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
logger.info(f"✅ 成功安装: {pip_requirement}")
|
|
||||||
self.install_log.append(f"成功安装: {pip_requirement}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.error(f"❌ 安装失败: {pip_requirement}")
|
|
||||||
logger.error(f"错误输出: {result.stderr}")
|
|
||||||
self.install_log.append(f"安装失败: {pip_requirement} - {result.stderr}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
logger.error(f"❌ 安装超时: {pip_requirement}")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ 安装异常: {pip_requirement} - {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def generate_requirements_file(
|
|
||||||
self, plugins_dependencies: List[List[PythonDependency]], output_path: str = "plugin_requirements.txt"
|
|
||||||
) -> bool:
|
|
||||||
"""生成插件依赖的requirements文件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plugins_dependencies: 所有插件的依赖列表
|
|
||||||
output_path: 输出文件路径
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 生成是否成功
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
all_deps = {}
|
|
||||||
|
|
||||||
# 合并所有插件的依赖
|
|
||||||
for plugin_deps in plugins_dependencies:
|
|
||||||
for dep in plugin_deps:
|
|
||||||
key = dep.install_name
|
|
||||||
if key in all_deps:
|
|
||||||
# 如果已存在,可以添加版本兼容性检查逻辑
|
|
||||||
existing = all_deps[key]
|
|
||||||
if dep.version and existing.version != dep.version:
|
|
||||||
logger.warning(f"依赖版本冲突: {key} ({existing.version} vs {dep.version})")
|
|
||||||
else:
|
|
||||||
all_deps[key] = dep
|
|
||||||
|
|
||||||
# 写入requirements文件
|
|
||||||
with open(output_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write("# 插件依赖包自动生成\n")
|
|
||||||
f.write("# Auto-generated plugin dependencies\n\n")
|
|
||||||
|
|
||||||
# 按包名排序
|
|
||||||
sorted_deps = sorted(all_deps.values(), key=lambda x: x.install_name)
|
|
||||||
|
|
||||||
for dep in sorted_deps:
|
|
||||||
requirement = dep.get_pip_requirement()
|
|
||||||
if dep.description:
|
|
||||||
f.write(f"# {dep.description}\n")
|
|
||||||
if dep.optional:
|
|
||||||
f.write("# Optional dependency\n")
|
|
||||||
f.write(f"{requirement}\n\n")
|
|
||||||
|
|
||||||
logger.info(f"已生成插件依赖文件: {output_path} ({len(all_deps)} 个包)")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"生成requirements文件失败: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_install_summary(self) -> Dict[str, Any]:
|
|
||||||
"""获取安装摘要"""
|
|
||||||
return {
|
|
||||||
"install_log": self.install_log.copy(),
|
|
||||||
"failed_installs": self.failed_installs.copy(),
|
|
||||||
"total_attempts": len(self.install_log),
|
|
||||||
"failed_count": len(self.failed_installs),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# 全局依赖管理器实例
|
|
||||||
dependency_manager = DependencyManager()
|
|
||||||
@@ -13,6 +13,8 @@ class GlobalAnnouncementManager:
|
|||||||
self._user_disabled_commands: Dict[str, List[str]] = {}
|
self._user_disabled_commands: Dict[str, List[str]] = {}
|
||||||
# 用户禁用的事件处理器,chat_id -> [handler_name]
|
# 用户禁用的事件处理器,chat_id -> [handler_name]
|
||||||
self._user_disabled_event_handlers: Dict[str, List[str]] = {}
|
self._user_disabled_event_handlers: Dict[str, List[str]] = {}
|
||||||
|
# 用户禁用的工具,chat_id -> [tool_name]
|
||||||
|
self._user_disabled_tools: Dict[str, List[str]] = {}
|
||||||
|
|
||||||
def disable_specific_chat_action(self, chat_id: str, action_name: str) -> bool:
|
def disable_specific_chat_action(self, chat_id: str, action_name: str) -> bool:
|
||||||
"""禁用特定聊天的某个动作"""
|
"""禁用特定聊天的某个动作"""
|
||||||
@@ -77,6 +79,27 @@ class GlobalAnnouncementManager:
|
|||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def disable_specific_chat_tool(self, chat_id: str, tool_name: str) -> bool:
|
||||||
|
"""禁用特定聊天的某个工具"""
|
||||||
|
if chat_id not in self._user_disabled_tools:
|
||||||
|
self._user_disabled_tools[chat_id] = []
|
||||||
|
if tool_name in self._user_disabled_tools[chat_id]:
|
||||||
|
logger.warning(f"工具 {tool_name} 已经被禁用")
|
||||||
|
return False
|
||||||
|
self._user_disabled_tools[chat_id].append(tool_name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def enable_specific_chat_tool(self, chat_id: str, tool_name: str) -> bool:
|
||||||
|
"""启用特定聊天的某个工具"""
|
||||||
|
if chat_id in self._user_disabled_tools:
|
||||||
|
try:
|
||||||
|
self._user_disabled_tools[chat_id].remove(tool_name)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
logger.warning(f"工具 {tool_name} 不在禁用列表中")
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
def get_disabled_chat_actions(self, chat_id: str) -> List[str]:
|
def get_disabled_chat_actions(self, chat_id: str) -> List[str]:
|
||||||
"""获取特定聊天禁用的所有动作"""
|
"""获取特定聊天禁用的所有动作"""
|
||||||
return self._user_disabled_actions.get(chat_id, []).copy()
|
return self._user_disabled_actions.get(chat_id, []).copy()
|
||||||
@@ -88,6 +111,10 @@ class GlobalAnnouncementManager:
|
|||||||
def get_disabled_chat_event_handlers(self, chat_id: str) -> List[str]:
|
def get_disabled_chat_event_handlers(self, chat_id: str) -> List[str]:
|
||||||
"""获取特定聊天禁用的所有事件处理器"""
|
"""获取特定聊天禁用的所有事件处理器"""
|
||||||
return self._user_disabled_event_handlers.get(chat_id, []).copy()
|
return self._user_disabled_event_handlers.get(chat_id, []).copy()
|
||||||
|
|
||||||
|
def get_disabled_chat_tools(self, chat_id: str) -> List[str]:
|
||||||
|
"""获取特定聊天禁用的所有工具"""
|
||||||
|
return self._user_disabled_tools.get(chat_id, []).copy()
|
||||||
|
|
||||||
|
|
||||||
global_announcement_manager = GlobalAnnouncementManager()
|
global_announcement_manager = GlobalAnnouncementManager()
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ from pathlib import Path
|
|||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system.base.plugin_base import PluginBase
|
from src.plugin_system.base.plugin_base import PluginBase
|
||||||
from src.plugin_system.base.component_types import ComponentType, PythonDependency
|
from src.plugin_system.base.component_types import ComponentType
|
||||||
from src.plugin_system.utils.manifest_utils import VersionComparator
|
from src.plugin_system.utils.manifest_utils import VersionComparator
|
||||||
from .component_registry import component_registry
|
from .component_registry import component_registry
|
||||||
from .dependency_manager import dependency_manager
|
|
||||||
|
|
||||||
logger = get_logger("plugin_manager")
|
logger = get_logger("plugin_manager")
|
||||||
|
|
||||||
@@ -207,104 +206,6 @@ class PluginManager:
|
|||||||
"""
|
"""
|
||||||
return self.loaded_plugins.get(plugin_name)
|
return self.loaded_plugins.get(plugin_name)
|
||||||
|
|
||||||
def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, Any]:
|
|
||||||
"""检查所有插件的Python依赖包
|
|
||||||
|
|
||||||
Args:
|
|
||||||
auto_install: 是否自动安装缺失的依赖包
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict[str, any]: 检查结果摘要
|
|
||||||
"""
|
|
||||||
logger.info("开始检查所有插件的Python依赖包...")
|
|
||||||
|
|
||||||
all_required_missing: List[PythonDependency] = []
|
|
||||||
all_optional_missing: List[PythonDependency] = []
|
|
||||||
plugin_status = {}
|
|
||||||
|
|
||||||
for plugin_name in self.loaded_plugins:
|
|
||||||
plugin_info = component_registry.get_plugin_info(plugin_name)
|
|
||||||
if not plugin_info or not plugin_info.python_dependencies:
|
|
||||||
plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []}
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.info(f"检查插件 {plugin_name} 的依赖...")
|
|
||||||
|
|
||||||
missing_required, missing_optional = dependency_manager.check_dependencies(plugin_info.python_dependencies)
|
|
||||||
|
|
||||||
if missing_required:
|
|
||||||
all_required_missing.extend(missing_required)
|
|
||||||
plugin_status[plugin_name] = {
|
|
||||||
"status": "missing_required",
|
|
||||||
"missing": [dep.package_name for dep in missing_required],
|
|
||||||
"optional_missing": [dep.package_name for dep in missing_optional],
|
|
||||||
}
|
|
||||||
logger.error(f"插件 {plugin_name} 缺少必需依赖: {[dep.package_name for dep in missing_required]}")
|
|
||||||
elif missing_optional:
|
|
||||||
all_optional_missing.extend(missing_optional)
|
|
||||||
plugin_status[plugin_name] = {
|
|
||||||
"status": "missing_optional",
|
|
||||||
"missing": [],
|
|
||||||
"optional_missing": [dep.package_name for dep in missing_optional],
|
|
||||||
}
|
|
||||||
logger.warning(f"插件 {plugin_name} 缺少可选依赖: {[dep.package_name for dep in missing_optional]}")
|
|
||||||
else:
|
|
||||||
plugin_status[plugin_name] = {"status": "ok", "missing": []}
|
|
||||||
logger.info(f"插件 {plugin_name} 依赖检查通过")
|
|
||||||
|
|
||||||
# 汇总结果
|
|
||||||
total_missing = len({dep.package_name for dep in all_required_missing})
|
|
||||||
total_optional_missing = len({dep.package_name for dep in all_optional_missing})
|
|
||||||
|
|
||||||
logger.info(f"依赖检查完成 - 缺少必需包: {total_missing}个, 缺少可选包: {total_optional_missing}个")
|
|
||||||
|
|
||||||
# 如果需要自动安装
|
|
||||||
install_success = True
|
|
||||||
if auto_install and all_required_missing:
|
|
||||||
unique_required = {dep.package_name: dep for dep in all_required_missing}
|
|
||||||
logger.info(f"开始自动安装 {len(unique_required)} 个必需依赖包...")
|
|
||||||
install_success = dependency_manager.install_dependencies(list(unique_required.values()), auto_install=True)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"total_plugins_checked": len(plugin_status),
|
|
||||||
"plugins_with_missing_required": len(
|
|
||||||
[p for p in plugin_status.values() if p["status"] == "missing_required"]
|
|
||||||
),
|
|
||||||
"plugins_with_missing_optional": len(
|
|
||||||
[p for p in plugin_status.values() if p["status"] == "missing_optional"]
|
|
||||||
),
|
|
||||||
"total_missing_required": total_missing,
|
|
||||||
"total_missing_optional": total_optional_missing,
|
|
||||||
"plugin_status": plugin_status,
|
|
||||||
"auto_install_attempted": auto_install and bool(all_required_missing),
|
|
||||||
"auto_install_success": install_success,
|
|
||||||
"install_summary": dependency_manager.get_install_summary(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_plugin_requirements(self, output_path: str = "plugin_requirements.txt") -> bool:
|
|
||||||
"""生成所有插件依赖的requirements文件
|
|
||||||
|
|
||||||
Args:
|
|
||||||
output_path: 输出文件路径
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 生成是否成功
|
|
||||||
"""
|
|
||||||
logger.info("开始生成插件依赖requirements文件...")
|
|
||||||
|
|
||||||
all_dependencies = []
|
|
||||||
|
|
||||||
for plugin_name in self.loaded_plugins:
|
|
||||||
plugin_info = component_registry.get_plugin_info(plugin_name)
|
|
||||||
if plugin_info and plugin_info.python_dependencies:
|
|
||||||
all_dependencies.append(plugin_info.python_dependencies)
|
|
||||||
|
|
||||||
if not all_dependencies:
|
|
||||||
logger.info("没有找到任何插件依赖")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return dependency_manager.generate_requirements_file(all_dependencies, output_path)
|
|
||||||
|
|
||||||
# === 查询方法 ===
|
# === 查询方法 ===
|
||||||
def list_loaded_plugins(self) -> List[str]:
|
def list_loaded_plugins(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
@@ -323,6 +224,18 @@ class PluginManager:
|
|||||||
list: 已注册的插件类名称列表。
|
list: 已注册的插件类名称列表。
|
||||||
"""
|
"""
|
||||||
return list(self.plugin_classes.keys())
|
return list(self.plugin_classes.keys())
|
||||||
|
|
||||||
|
def get_plugin_path(self, plugin_name: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
获取指定插件的路径。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_name: 插件名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[str]: 插件目录的绝对路径,如果插件不存在则返回None。
|
||||||
|
"""
|
||||||
|
return self.plugin_paths.get(plugin_name)
|
||||||
|
|
||||||
# === 私有方法 ===
|
# === 私有方法 ===
|
||||||
# == 目录管理 ==
|
# == 目录管理 ==
|
||||||
@@ -388,6 +301,7 @@ class PluginManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
module = module_from_spec(spec)
|
module = module_from_spec(spec)
|
||||||
|
module.__package__ = module_name # 设置模块包名
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
logger.debug(f"插件模块加载成功: {plugin_file}")
|
logger.debug(f"插件模块加载成功: {plugin_file}")
|
||||||
@@ -444,6 +358,7 @@ class PluginManager:
|
|||||||
stats = component_registry.get_registry_stats()
|
stats = component_registry.get_registry_stats()
|
||||||
action_count = stats.get("action_components", 0)
|
action_count = stats.get("action_components", 0)
|
||||||
command_count = stats.get("command_components", 0)
|
command_count = stats.get("command_components", 0)
|
||||||
|
tool_count = stats.get("tool_components", 0)
|
||||||
event_handler_count = stats.get("event_handlers", 0)
|
event_handler_count = stats.get("event_handlers", 0)
|
||||||
total_components = stats.get("total_components", 0)
|
total_components = stats.get("total_components", 0)
|
||||||
|
|
||||||
@@ -451,7 +366,7 @@ class PluginManager:
|
|||||||
if total_registered > 0:
|
if total_registered > 0:
|
||||||
logger.info("🎉 插件系统加载完成!")
|
logger.info("🎉 插件系统加载完成!")
|
||||||
logger.info(
|
logger.info(
|
||||||
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, EventHandler: {event_handler_count})"
|
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, EventHandler: {event_handler_count})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 显示详细的插件列表
|
# 显示详细的插件列表
|
||||||
@@ -486,6 +401,9 @@ class PluginManager:
|
|||||||
command_components = [
|
command_components = [
|
||||||
c for c in plugin_info.components if c.component_type == ComponentType.COMMAND
|
c for c in plugin_info.components if c.component_type == ComponentType.COMMAND
|
||||||
]
|
]
|
||||||
|
tool_components = [
|
||||||
|
c for c in plugin_info.components if c.component_type == ComponentType.TOOL
|
||||||
|
]
|
||||||
event_handler_components = [
|
event_handler_components = [
|
||||||
c for c in plugin_info.components if c.component_type == ComponentType.EVENT_HANDLER
|
c for c in plugin_info.components if c.component_type == ComponentType.EVENT_HANDLER
|
||||||
]
|
]
|
||||||
@@ -497,7 +415,9 @@ class PluginManager:
|
|||||||
if command_components:
|
if command_components:
|
||||||
command_names = [c.name for c in command_components]
|
command_names = [c.name for c in command_components]
|
||||||
logger.info(f" ⚡ Command组件: {', '.join(command_names)}")
|
logger.info(f" ⚡ Command组件: {', '.join(command_names)}")
|
||||||
|
if tool_components:
|
||||||
|
tool_names = [c.name for c in tool_components]
|
||||||
|
logger.info(f" 🛠️ Tool组件: {', '.join(tool_names)}")
|
||||||
if event_handler_components:
|
if event_handler_components:
|
||||||
event_handler_names = [c.name for c in event_handler_components]
|
event_handler_names = [c.name for c in event_handler_components]
|
||||||
logger.info(f" 📢 EventHandler组件: {', '.join(event_handler_names)}")
|
logger.info(f" 📢 EventHandler组件: {', '.join(event_handler_names)}")
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
|
import json
|
||||||
|
import time
|
||||||
|
from typing import List, Dict, Tuple, Optional, Any
|
||||||
|
from src.plugin_system.apis.tool_api import get_llm_available_tool_definitions, get_tool_instance
|
||||||
|
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
||||||
from src.llm_models.utils_model import LLMRequest
|
from src.llm_models.utils_model import LLMRequest
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
import time
|
|
||||||
from src.common.logger 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.tools.tool_use import ToolUser
|
|
||||||
from src.chat.utils.json_utils import process_llm_tool_calls
|
from src.chat.utils.json_utils import process_llm_tool_calls
|
||||||
from typing import List, Dict, Tuple, Optional
|
|
||||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
logger = get_logger("tool_executor")
|
logger = get_logger("tool_use")
|
||||||
|
|
||||||
|
|
||||||
def init_tool_executor_prompt():
|
def init_tool_executor_prompt():
|
||||||
@@ -28,6 +30,10 @@ If you need to use a tool, please directly call the corresponding tool function.
|
|||||||
Prompt(tool_executor_prompt, "tool_executor_prompt")
|
Prompt(tool_executor_prompt, "tool_executor_prompt")
|
||||||
|
|
||||||
|
|
||||||
|
# 初始化提示词
|
||||||
|
init_tool_executor_prompt()
|
||||||
|
|
||||||
|
|
||||||
class ToolExecutor:
|
class ToolExecutor:
|
||||||
"""独立的工具执行器组件
|
"""独立的工具执行器组件
|
||||||
|
|
||||||
@@ -51,9 +57,6 @@ class ToolExecutor:
|
|||||||
request_type="tool_executor",
|
request_type="tool_executor",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 初始化工具实例
|
|
||||||
self.tool_instance = ToolUser()
|
|
||||||
|
|
||||||
# 缓存配置
|
# 缓存配置
|
||||||
self.enable_cache = enable_cache
|
self.enable_cache = enable_cache
|
||||||
self.cache_ttl = cache_ttl
|
self.cache_ttl = cache_ttl
|
||||||
@@ -73,7 +76,7 @@ class ToolExecutor:
|
|||||||
return_details: 是否返回详细信息(使用的工具列表和提示词)
|
return_details: 是否返回详细信息(使用的工具列表和提示词)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
如果return_details为False: List[Dict] - 工具执行结果列表
|
如果return_details为False: Tuple[List[Dict], List[str], str] - (工具执行结果列表, 空, 空)
|
||||||
如果return_details为True: Tuple[List[Dict], List[str], str] - (结果列表, 使用的工具, 提示词)
|
如果return_details为True: Tuple[List[Dict], List[str], str] - (结果列表, 使用的工具, 提示词)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -82,15 +85,15 @@ class ToolExecutor:
|
|||||||
if cached_result := self._get_from_cache(cache_key):
|
if cached_result := self._get_from_cache(cache_key):
|
||||||
logger.info(f"{self.log_prefix}使用缓存结果,跳过工具执行")
|
logger.info(f"{self.log_prefix}使用缓存结果,跳过工具执行")
|
||||||
if not return_details:
|
if not return_details:
|
||||||
return cached_result, [], "使用缓存结果"
|
return cached_result, [], ""
|
||||||
|
|
||||||
# 从缓存结果中提取工具名称
|
# 从缓存结果中提取工具名称
|
||||||
used_tools = [result.get("tool_name", "unknown") for result in cached_result]
|
used_tools = [result.get("tool_name", "unknown") for result in cached_result]
|
||||||
return cached_result, used_tools, "使用缓存结果"
|
return cached_result, used_tools, ""
|
||||||
|
|
||||||
# 缓存未命中,执行工具调用
|
# 缓存未命中,执行工具调用
|
||||||
# 获取可用工具
|
# 获取可用工具
|
||||||
tools = self.tool_instance._define_tools()
|
tools = self._get_tool_definitions()
|
||||||
|
|
||||||
# 获取当前时间
|
# 获取当前时间
|
||||||
time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
@@ -112,6 +115,7 @@ class ToolExecutor:
|
|||||||
# 调用LLM进行工具决策
|
# 调用LLM进行工具决策
|
||||||
response, other_info = await self.llm_model.generate_response_async(prompt=prompt, tools=tools)
|
response, other_info = await self.llm_model.generate_response_async(prompt=prompt, tools=tools)
|
||||||
|
|
||||||
|
# TODO: 在APIADA加入后完全修复这里!
|
||||||
# 解析LLM响应
|
# 解析LLM响应
|
||||||
if len(other_info) == 3:
|
if len(other_info) == 3:
|
||||||
reasoning_content, model_name, tool_calls = other_info
|
reasoning_content, model_name, tool_calls = other_info
|
||||||
@@ -133,6 +137,11 @@ class ToolExecutor:
|
|||||||
return tool_results, used_tools, prompt
|
return tool_results, used_tools, prompt
|
||||||
else:
|
else:
|
||||||
return tool_results, [], ""
|
return tool_results, [], ""
|
||||||
|
|
||||||
|
def _get_tool_definitions(self) -> List[Dict[str, Any]]:
|
||||||
|
all_tools = get_llm_available_tool_definitions()
|
||||||
|
user_disabled_tools = global_announcement_manager.get_disabled_chat_tools(self.chat_id)
|
||||||
|
return [parameters for name, parameters in all_tools if name not in user_disabled_tools]
|
||||||
|
|
||||||
async def _execute_tool_calls(self, tool_calls) -> Tuple[List[Dict], List[str]]:
|
async def _execute_tool_calls(self, tool_calls) -> Tuple[List[Dict], List[str]]:
|
||||||
"""执行工具调用
|
"""执行工具调用
|
||||||
@@ -172,7 +181,7 @@ class ToolExecutor:
|
|||||||
logger.debug(f"{self.log_prefix}执行工具: {tool_name}")
|
logger.debug(f"{self.log_prefix}执行工具: {tool_name}")
|
||||||
|
|
||||||
# 执行工具
|
# 执行工具
|
||||||
result = await self.tool_instance._execute_tool_call(tool_call)
|
result = await self._execute_tool_call(tool_call)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
tool_info = {
|
tool_info = {
|
||||||
@@ -205,6 +214,45 @@ class ToolExecutor:
|
|||||||
|
|
||||||
return tool_results, used_tools
|
return tool_results, used_tools
|
||||||
|
|
||||||
|
async def _execute_tool_call(self, tool_call: Dict[str, Any]) -> Optional[Dict]:
|
||||||
|
# sourcery skip: use-assigned-variable
|
||||||
|
"""执行单个工具调用
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_call: 工具调用对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Dict]: 工具调用结果,如果失败则返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
function_name = tool_call["function"]["name"]
|
||||||
|
function_args = json.loads(tool_call["function"]["arguments"])
|
||||||
|
function_args["llm_called"] = True # 标记为LLM调用
|
||||||
|
|
||||||
|
# 获取对应工具实例
|
||||||
|
tool_instance = get_tool_instance(function_name)
|
||||||
|
if not tool_instance:
|
||||||
|
logger.warning(f"未知工具名称: {function_name}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 执行工具
|
||||||
|
result = await tool_instance.execute(function_args)
|
||||||
|
if result:
|
||||||
|
# 直接使用 function_name 作为 tool_type
|
||||||
|
tool_type = function_name
|
||||||
|
|
||||||
|
return {
|
||||||
|
"tool_call_id": tool_call["id"],
|
||||||
|
"role": "tool",
|
||||||
|
"name": function_name,
|
||||||
|
"type": tool_type,
|
||||||
|
"content": result["content"],
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"执行工具调用时发生错误: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _generate_cache_key(self, target_message: str, chat_history: str, sender: str) -> str:
|
def _generate_cache_key(self, target_message: str, chat_history: str, sender: str) -> str:
|
||||||
"""生成缓存键
|
"""生成缓存键
|
||||||
|
|
||||||
@@ -272,15 +320,6 @@ class ToolExecutor:
|
|||||||
if expired_keys:
|
if expired_keys:
|
||||||
logger.debug(f"{self.log_prefix}清理了{len(expired_keys)}个过期缓存")
|
logger.debug(f"{self.log_prefix}清理了{len(expired_keys)}个过期缓存")
|
||||||
|
|
||||||
def get_available_tools(self) -> List[str]:
|
|
||||||
"""获取可用工具列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: 可用工具名称列表
|
|
||||||
"""
|
|
||||||
tools = self.tool_instance._define_tools()
|
|
||||||
return [tool.get("function", {}).get("name", "unknown") for tool in tools]
|
|
||||||
|
|
||||||
async def execute_specific_tool(
|
async def execute_specific_tool(
|
||||||
self, tool_name: str, tool_args: Dict, validate_args: bool = True
|
self, tool_name: str, tool_args: Dict, validate_args: bool = True
|
||||||
) -> Optional[Dict]:
|
) -> Optional[Dict]:
|
||||||
@@ -299,7 +338,7 @@ class ToolExecutor:
|
|||||||
|
|
||||||
logger.info(f"{self.log_prefix}直接执行工具: {tool_name}")
|
logger.info(f"{self.log_prefix}直接执行工具: {tool_name}")
|
||||||
|
|
||||||
result = await self.tool_instance._execute_tool_call(tool_call)
|
result = await self._execute_tool_call(tool_call)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
tool_info = {
|
tool_info = {
|
||||||
@@ -366,12 +405,8 @@ class ToolExecutor:
|
|||||||
logger.info(f"{self.log_prefix}缓存TTL修改为: {cache_ttl}")
|
logger.info(f"{self.log_prefix}缓存TTL修改为: {cache_ttl}")
|
||||||
|
|
||||||
|
|
||||||
# 初始化提示词
|
|
||||||
init_tool_executor_prompt()
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
使用示例:
|
ToolExecutor使用示例:
|
||||||
|
|
||||||
# 1. 基础使用 - 从聊天消息执行工具(启用缓存,默认TTL=3)
|
# 1. 基础使用 - 从聊天消息执行工具(启用缓存,默认TTL=3)
|
||||||
executor = ToolExecutor(executor_id="my_executor")
|
executor = ToolExecutor(executor_id="my_executor")
|
||||||
@@ -400,7 +435,6 @@ result = await executor.execute_specific_tool(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 6. 缓存管理
|
# 6. 缓存管理
|
||||||
available_tools = executor.get_available_tools()
|
|
||||||
cache_status = executor.get_cache_status() # 查看缓存状态
|
cache_status = executor.get_cache_status() # 查看缓存状态
|
||||||
executor.clear_cache() # 清空缓存
|
executor.clear_cache() # 清空缓存
|
||||||
executor.set_cache_config(cache_ttl=5) # 动态修改缓存配置
|
executor.set_cache_config(cache_ttl=5) # 动态修改缓存配置
|
||||||
@@ -163,12 +163,11 @@ class VersionComparator:
|
|||||||
version_normalized, max_normalized
|
version_normalized, max_normalized
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_compatible:
|
if not is_compatible:
|
||||||
logger.info(f"版本兼容性检查:{compat_msg}")
|
|
||||||
return True, compat_msg
|
|
||||||
else:
|
|
||||||
return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized},且无兼容性映射"
|
return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized},且无兼容性映射"
|
||||||
|
|
||||||
|
logger.info(f"版本兼容性检查:{compat_msg}")
|
||||||
|
return True, compat_msg
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -358,14 +357,10 @@ class ManifestValidator:
|
|||||||
|
|
||||||
if self.validation_errors:
|
if self.validation_errors:
|
||||||
report.append("❌ 验证错误:")
|
report.append("❌ 验证错误:")
|
||||||
for error in self.validation_errors:
|
report.extend(f" - {error}" for error in self.validation_errors)
|
||||||
report.append(f" - {error}")
|
|
||||||
|
|
||||||
if self.validation_warnings:
|
if self.validation_warnings:
|
||||||
report.append("⚠️ 验证警告:")
|
report.append("⚠️ 验证警告:")
|
||||||
for warning in self.validation_warnings:
|
report.extend(f" - {warning}" for warning in self.validation_warnings)
|
||||||
report.append(f" - {warning}")
|
|
||||||
|
|
||||||
if not self.validation_errors and not self.validation_warnings:
|
if not self.validation_errors and not self.validation_warnings:
|
||||||
report.append("✅ Manifest文件验证通过")
|
report.append("✅ Manifest文件验证通过")
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,6 @@
|
|||||||
"is_built_in": true,
|
"is_built_in": true,
|
||||||
"plugin_type": "action_provider",
|
"plugin_type": "action_provider",
|
||||||
"components": [
|
"components": [
|
||||||
{
|
|
||||||
"type": "action",
|
|
||||||
"name": "reply",
|
|
||||||
"description": "参与聊天回复,发送文本进行表达"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "action",
|
"type": "action",
|
||||||
"name": "no_reply",
|
"name": "no_reply",
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from src.common.logger import get_logger
|
|||||||
|
|
||||||
# 导入API模块 - 标准Python包方式
|
# 导入API模块 - 标准Python包方式
|
||||||
from src.plugin_system.apis import emoji_api, llm_api, message_api
|
from src.plugin_system.apis import emoji_api, llm_api, message_api
|
||||||
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
# 注释:不再需要导入NoReplyAction,因为计数器管理已移至heartFC_chat.py
|
||||||
|
# from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
|
|
||||||
|
|
||||||
@@ -20,10 +21,14 @@ class EmojiAction(BaseAction):
|
|||||||
"""表情动作 - 发送表情包"""
|
"""表情动作 - 发送表情包"""
|
||||||
|
|
||||||
# 激活设置
|
# 激活设置
|
||||||
activation_type = ActionActivationType.RANDOM
|
if global_config.emoji.emoji_activate_type == "llm":
|
||||||
|
activation_type = ActionActivationType.LLM_JUDGE
|
||||||
|
random_activation_probability = 0
|
||||||
|
else:
|
||||||
|
activation_type = ActionActivationType.RANDOM
|
||||||
|
random_activation_probability = global_config.emoji.emoji_chance
|
||||||
mode_enable = ChatMode.ALL
|
mode_enable = ChatMode.ALL
|
||||||
parallel_action = True
|
parallel_action = True
|
||||||
random_activation_probability = 0.2 # 默认值,可通过配置覆盖
|
|
||||||
|
|
||||||
# 动作基本信息
|
# 动作基本信息
|
||||||
action_name = "emoji"
|
action_name = "emoji"
|
||||||
@@ -115,7 +120,7 @@ class EmojiAction(BaseAction):
|
|||||||
logger.error(f"{self.log_prefix} 未找到'utils_small'模型配置,无法调用LLM")
|
logger.error(f"{self.log_prefix} 未找到'utils_small'模型配置,无法调用LLM")
|
||||||
return False, "未找到'utils_small'模型配置"
|
return False, "未找到'utils_small'模型配置"
|
||||||
|
|
||||||
success, chosen_emotion, _, _ = await llm_api.generate_with_model(
|
success, chosen_emotion = await llm_api.generate_with_model(
|
||||||
prompt, model_config=chat_model_config, request_type="emoji"
|
prompt, model_config=chat_model_config, request_type="emoji"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -143,8 +148,8 @@ class EmojiAction(BaseAction):
|
|||||||
logger.error(f"{self.log_prefix} 表情包发送失败")
|
logger.error(f"{self.log_prefix} 表情包发送失败")
|
||||||
return False, "表情包发送失败"
|
return False, "表情包发送失败"
|
||||||
|
|
||||||
# 重置NoReplyAction的连续计数器
|
# 注释:重置NoReplyAction的连续计数器现在由heartFC_chat.py统一管理
|
||||||
NoReplyAction.reset_consecutive_count()
|
# NoReplyAction.reset_consecutive_count()
|
||||||
|
|
||||||
return True, f"发送表情包: {emoji_description}"
|
return True, f"发送表情包: {emoji_description}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from typing import Tuple
|
from typing import Tuple, List
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
# 导入新插件系统
|
# 导入新插件系统
|
||||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
||||||
@@ -17,11 +18,15 @@ logger = get_logger("no_reply_action")
|
|||||||
|
|
||||||
|
|
||||||
class NoReplyAction(BaseAction):
|
class NoReplyAction(BaseAction):
|
||||||
"""不回复动作,根据新消息的兴趣值或数量决定何时结束等待.
|
"""不回复动作,支持waiting和breaking两种形式.
|
||||||
|
|
||||||
新的等待逻辑:
|
waiting形式:
|
||||||
1. 新消息累计兴趣值超过阈值 (默认10) 则结束等待
|
- 只要有新消息就结束动作
|
||||||
2. 累计新消息数量达到随机阈值 (默认5-10条) 则结束等待
|
- 记录新消息的兴趣度到列表(最多保留最近三项)
|
||||||
|
- 如果最近三次动作都是no_reply,且最近新消息列表兴趣度之和小于阈值,就进入breaking形式
|
||||||
|
|
||||||
|
breaking形式:
|
||||||
|
- 和原有逻辑一致,需要消息满足一定数量或累计一定兴趣值才结束动作
|
||||||
"""
|
"""
|
||||||
|
|
||||||
focus_activation_type = ActionActivationType.NEVER
|
focus_activation_type = ActionActivationType.NEVER
|
||||||
@@ -35,112 +40,45 @@ class NoReplyAction(BaseAction):
|
|||||||
|
|
||||||
# 连续no_reply计数器
|
# 连续no_reply计数器
|
||||||
_consecutive_count = 0
|
_consecutive_count = 0
|
||||||
|
|
||||||
|
# 最近三次no_reply的新消息兴趣度记录
|
||||||
|
_recent_interest_records: deque = deque(maxlen=3)
|
||||||
|
|
||||||
# 新增:兴趣值退出阈值
|
# 兴趣值退出阈值
|
||||||
_interest_exit_threshold = 3.0
|
_interest_exit_threshold = 3.0
|
||||||
# 新增:消息数量退出阈值
|
# 消息数量退出阈值
|
||||||
_min_exit_message_count = 5
|
_min_exit_message_count = 3
|
||||||
_max_exit_message_count = 10
|
_max_exit_message_count = 6
|
||||||
|
|
||||||
# 动作参数定义
|
# 动作参数定义
|
||||||
action_parameters = {}
|
action_parameters = {}
|
||||||
|
|
||||||
# 动作使用场景
|
# 动作使用场景
|
||||||
action_require = ["你发送了消息,目前无人回复"]
|
action_require = [""]
|
||||||
|
|
||||||
# 关联类型
|
# 关联类型
|
||||||
associated_types = []
|
associated_types = []
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
async def execute(self) -> Tuple[bool, str]:
|
||||||
"""执行不回复动作"""
|
"""执行不回复动作"""
|
||||||
import asyncio
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 增加连续计数
|
|
||||||
NoReplyAction._consecutive_count += 1
|
|
||||||
count = NoReplyAction._consecutive_count
|
|
||||||
|
|
||||||
reason = self.action_data.get("reason", "")
|
reason = self.action_data.get("reason", "")
|
||||||
start_time = self.action_data.get("loop_start_time", time.time())
|
start_time = self.action_data.get("loop_start_time", time.time())
|
||||||
check_interval = 0.6 # 每秒检查一次
|
check_interval = 0.6
|
||||||
|
|
||||||
# 随机生成本次等待需要的新消息数量阈值
|
# 判断使用哪种形式
|
||||||
exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count)
|
form_type = self._determine_form_type()
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix} 本次no_reply需要 {exit_message_count_threshold} 条新消息或累计兴趣值超过 {self._interest_exit_threshold} 才能打断"
|
logger.info(f"{self.log_prefix} 选择不回复(第{NoReplyAction._consecutive_count + 1}次),使用{form_type}形式,原因: {reason}")
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始摸鱼,原因: {reason}")
|
# 增加连续计数(在确定要执行no_reply时才增加)
|
||||||
|
NoReplyAction._consecutive_count += 1
|
||||||
|
|
||||||
# 进入等待状态
|
if form_type == "waiting":
|
||||||
while True:
|
return await self._execute_waiting_form(start_time, check_interval)
|
||||||
current_time = time.time()
|
else:
|
||||||
elapsed_time = current_time - start_time
|
return await self._execute_breaking_form(start_time, check_interval)
|
||||||
|
|
||||||
# 1. 检查新消息
|
|
||||||
recent_messages_dict = message_api.get_messages_by_time_in_chat(
|
|
||||||
chat_id=self.chat_id,
|
|
||||||
start_time=start_time,
|
|
||||||
end_time=current_time,
|
|
||||||
filter_mai=True,
|
|
||||||
filter_command=True,
|
|
||||||
)
|
|
||||||
new_message_count = len(recent_messages_dict)
|
|
||||||
|
|
||||||
# 2. 检查消息数量是否达到阈值
|
|
||||||
talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id)
|
|
||||||
if new_message_count >= exit_message_count_threshold / talk_frequency:
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{exit_message_count_threshold / talk_frequency}),结束等待"
|
|
||||||
)
|
|
||||||
exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复"
|
|
||||||
await self.store_action_info(
|
|
||||||
action_build_into_prompt=False,
|
|
||||||
action_prompt_display=exit_reason,
|
|
||||||
action_done=True,
|
|
||||||
)
|
|
||||||
return True, f"累计消息数量达到{new_message_count}条,结束等待 (等待时间: {elapsed_time:.1f}秒)"
|
|
||||||
|
|
||||||
# 3. 检查累计兴趣值
|
|
||||||
if new_message_count > 0:
|
|
||||||
accumulated_interest = 0.0
|
|
||||||
for msg_dict in recent_messages_dict:
|
|
||||||
text = msg_dict.get("processed_plain_text", "")
|
|
||||||
interest_value = msg_dict.get("interest_value", 0.0)
|
|
||||||
if text:
|
|
||||||
accumulated_interest += interest_value
|
|
||||||
|
|
||||||
talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id)
|
|
||||||
# 只在兴趣值变化时输出log
|
|
||||||
if not hasattr(self, "_last_accumulated_interest") or accumulated_interest != self._last_accumulated_interest:
|
|
||||||
logger.info(f"{self.log_prefix} 当前累计兴趣值: {accumulated_interest:.2f}, 当前聊天频率: {talk_frequency:.2f}")
|
|
||||||
self._last_accumulated_interest = accumulated_interest
|
|
||||||
|
|
||||||
if accumulated_interest >= self._interest_exit_threshold / talk_frequency:
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix} 累计兴趣值达到{accumulated_interest:.2f}(>{self._interest_exit_threshold / talk_frequency}),结束等待"
|
|
||||||
)
|
|
||||||
exit_reason = f"{global_config.bot.nickname}(你)感觉到了大家浓厚的兴趣(兴趣值{accumulated_interest:.1f}),决定重新加入讨论"
|
|
||||||
await self.store_action_info(
|
|
||||||
action_build_into_prompt=False,
|
|
||||||
action_prompt_display=exit_reason,
|
|
||||||
action_done=True,
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
True,
|
|
||||||
f"累计兴趣值达到{accumulated_interest:.2f},结束等待 (等待时间: {elapsed_time:.1f}秒)",
|
|
||||||
)
|
|
||||||
|
|
||||||
# 每10秒输出一次等待状态
|
|
||||||
if int(elapsed_time) > 0 and int(elapsed_time) % 10 == 0:
|
|
||||||
logger.debug(
|
|
||||||
f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,累计{new_message_count}条消息,继续等待..."
|
|
||||||
)
|
|
||||||
# 使用 asyncio.sleep(1) 来避免在同一秒内重复打印日志
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
# 短暂等待后继续检查
|
|
||||||
await asyncio.sleep(check_interval)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}")
|
logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}")
|
||||||
@@ -153,8 +91,191 @@ class NoReplyAction(BaseAction):
|
|||||||
)
|
)
|
||||||
return False, f"不回复动作执行失败: {e}"
|
return False, f"不回复动作执行失败: {e}"
|
||||||
|
|
||||||
|
def _determine_form_type(self) -> str:
|
||||||
|
"""判断使用哪种形式的no_reply"""
|
||||||
|
# 如果连续no_reply次数少于3次,使用waiting形式
|
||||||
|
if NoReplyAction._consecutive_count < 3:
|
||||||
|
return "waiting"
|
||||||
|
|
||||||
|
# 如果最近三次记录不足,使用waiting形式
|
||||||
|
if len(NoReplyAction._recent_interest_records) < 3:
|
||||||
|
return "waiting"
|
||||||
|
|
||||||
|
# 计算最近三次记录的兴趣度总和
|
||||||
|
total_recent_interest = sum(NoReplyAction._recent_interest_records)
|
||||||
|
|
||||||
|
# 获取当前聊天频率和意愿系数
|
||||||
|
talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id)
|
||||||
|
willing_amplifier = global_config.chat.willing_amplifier
|
||||||
|
|
||||||
|
# 计算调整后的阈值
|
||||||
|
adjusted_threshold = self._interest_exit_threshold / talk_frequency / willing_amplifier
|
||||||
|
|
||||||
|
logger.info(f"{self.log_prefix} 最近三次兴趣度总和: {total_recent_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}")
|
||||||
|
|
||||||
|
# 如果兴趣度总和小于阈值,进入breaking形式
|
||||||
|
if total_recent_interest < adjusted_threshold:
|
||||||
|
logger.info(f"{self.log_prefix} 兴趣度不足,进入breaking形式")
|
||||||
|
return "breaking"
|
||||||
|
else:
|
||||||
|
logger.info(f"{self.log_prefix} 兴趣度充足,继续使用waiting形式")
|
||||||
|
return "waiting"
|
||||||
|
|
||||||
|
async def _execute_waiting_form(self, start_time: float, check_interval: float) -> Tuple[bool, str]:
|
||||||
|
"""执行waiting形式的no_reply"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
logger.info(f"{self.log_prefix} 进入waiting形式,等待任何新消息")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
current_time = time.time()
|
||||||
|
elapsed_time = current_time - start_time
|
||||||
|
|
||||||
|
# 检查新消息
|
||||||
|
recent_messages_dict = message_api.get_messages_by_time_in_chat(
|
||||||
|
chat_id=self.chat_id,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=current_time,
|
||||||
|
filter_mai=True,
|
||||||
|
filter_command=True,
|
||||||
|
)
|
||||||
|
new_message_count = len(recent_messages_dict)
|
||||||
|
|
||||||
|
# waiting形式:只要有新消息就结束
|
||||||
|
if new_message_count > 0:
|
||||||
|
# 计算新消息的总兴趣度
|
||||||
|
total_interest = 0.0
|
||||||
|
for msg_dict in recent_messages_dict:
|
||||||
|
interest_value = msg_dict.get("interest_value", 0.0)
|
||||||
|
if msg_dict.get("processed_plain_text", ""):
|
||||||
|
total_interest += interest_value * global_config.chat.willing_amplifier
|
||||||
|
|
||||||
|
# 记录到最近兴趣度列表
|
||||||
|
NoReplyAction._recent_interest_records.append(total_interest)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix} waiting形式检测到{new_message_count}条新消息,总兴趣度: {total_interest:.2f},结束等待"
|
||||||
|
)
|
||||||
|
|
||||||
|
exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复"
|
||||||
|
await self.store_action_info(
|
||||||
|
action_build_into_prompt=False,
|
||||||
|
action_prompt_display=exit_reason,
|
||||||
|
action_done=True,
|
||||||
|
)
|
||||||
|
return True, f"waiting形式检测到{new_message_count}条新消息,结束等待 (等待时间: {elapsed_time:.1f}秒)"
|
||||||
|
|
||||||
|
# 每10秒输出一次等待状态
|
||||||
|
if int(elapsed_time) > 0 and int(elapsed_time) % 10 == 0:
|
||||||
|
logger.debug(f"{self.log_prefix} waiting形式已等待{elapsed_time:.0f}秒,继续等待新消息...")
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
# 短暂等待后继续检查
|
||||||
|
await asyncio.sleep(check_interval)
|
||||||
|
|
||||||
|
async def _execute_breaking_form(self, start_time: float, check_interval: float) -> Tuple[bool, str]:
|
||||||
|
"""执行breaking形式的no_reply(原有逻辑)"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# 随机生成本次等待需要的新消息数量阈值
|
||||||
|
exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count)
|
||||||
|
|
||||||
|
logger.info(f"{self.log_prefix} 进入breaking形式,需要{exit_message_count_threshold}条消息或足够兴趣度")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
current_time = time.time()
|
||||||
|
elapsed_time = current_time - start_time
|
||||||
|
|
||||||
|
# 检查新消息
|
||||||
|
recent_messages_dict = message_api.get_messages_by_time_in_chat(
|
||||||
|
chat_id=self.chat_id,
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=current_time,
|
||||||
|
filter_mai=True,
|
||||||
|
filter_command=True,
|
||||||
|
)
|
||||||
|
new_message_count = len(recent_messages_dict)
|
||||||
|
|
||||||
|
# 检查消息数量是否达到阈值
|
||||||
|
talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id)
|
||||||
|
modified_exit_count_threshold = (exit_message_count_threshold / talk_frequency) / global_config.chat.willing_amplifier
|
||||||
|
|
||||||
|
if new_message_count >= modified_exit_count_threshold:
|
||||||
|
# 记录兴趣度到列表
|
||||||
|
total_interest = 0.0
|
||||||
|
for msg_dict in recent_messages_dict:
|
||||||
|
interest_value = msg_dict.get("interest_value", 0.0)
|
||||||
|
if msg_dict.get("processed_plain_text", ""):
|
||||||
|
total_interest += interest_value * global_config.chat.willing_amplifier
|
||||||
|
|
||||||
|
NoReplyAction._recent_interest_records.append(total_interest)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix} breaking形式累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold}),结束等待"
|
||||||
|
)
|
||||||
|
exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复"
|
||||||
|
await self.store_action_info(
|
||||||
|
action_build_into_prompt=False,
|
||||||
|
action_prompt_display=exit_reason,
|
||||||
|
action_done=True,
|
||||||
|
)
|
||||||
|
return True, f"breaking形式累计消息数量达到{new_message_count}条,结束等待 (等待时间: {elapsed_time:.1f}秒)"
|
||||||
|
|
||||||
|
# 检查累计兴趣值
|
||||||
|
if new_message_count > 0:
|
||||||
|
accumulated_interest = 0.0
|
||||||
|
for msg_dict in recent_messages_dict:
|
||||||
|
text = msg_dict.get("processed_plain_text", "")
|
||||||
|
interest_value = msg_dict.get("interest_value", 0.0)
|
||||||
|
if text:
|
||||||
|
accumulated_interest += interest_value * global_config.chat.willing_amplifier
|
||||||
|
|
||||||
|
# 只在兴趣值变化时输出log
|
||||||
|
if not hasattr(self, "_last_accumulated_interest") or accumulated_interest != self._last_accumulated_interest:
|
||||||
|
logger.info(f"{self.log_prefix} breaking形式当前累计兴趣值: {accumulated_interest:.2f}, 当前聊天频率: {talk_frequency:.2f}")
|
||||||
|
self._last_accumulated_interest = accumulated_interest
|
||||||
|
|
||||||
|
if accumulated_interest >= self._interest_exit_threshold / talk_frequency:
|
||||||
|
# 记录兴趣度到列表
|
||||||
|
NoReplyAction._recent_interest_records.append(accumulated_interest)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix} breaking形式累计兴趣值达到{accumulated_interest:.2f}(>{self._interest_exit_threshold / talk_frequency}),结束等待"
|
||||||
|
)
|
||||||
|
exit_reason = f"{global_config.bot.nickname}(你)感觉到了大家浓厚的兴趣(兴趣值{accumulated_interest:.1f}),决定重新加入讨论"
|
||||||
|
await self.store_action_info(
|
||||||
|
action_build_into_prompt=False,
|
||||||
|
action_prompt_display=exit_reason,
|
||||||
|
action_done=True,
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
True,
|
||||||
|
f"breaking形式累计兴趣值达到{accumulated_interest:.2f},结束等待 (等待时间: {elapsed_time:.1f}秒)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 每10秒输出一次等待状态
|
||||||
|
if int(elapsed_time) > 0 and int(elapsed_time) % 10 == 0:
|
||||||
|
logger.debug(
|
||||||
|
f"{self.log_prefix} breaking形式已等待{elapsed_time:.0f}秒,累计{new_message_count}条消息,继续等待..."
|
||||||
|
)
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
# 短暂等待后继续检查
|
||||||
|
await asyncio.sleep(check_interval)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def reset_consecutive_count(cls):
|
def reset_consecutive_count(cls):
|
||||||
"""重置连续计数器"""
|
"""重置连续计数器和兴趣度记录"""
|
||||||
cls._consecutive_count = 0
|
cls._consecutive_count = 0
|
||||||
logger.debug("NoReplyAction连续计数器已重置")
|
cls._recent_interest_records.clear()
|
||||||
|
logger.debug("NoReplyAction连续计数器和兴趣度记录已重置")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_recent_interest_records(cls) -> List[float]:
|
||||||
|
"""获取最近的兴趣度记录"""
|
||||||
|
return list(cls._recent_interest_records)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_consecutive_count(cls) -> int:
|
||||||
|
"""获取连续计数"""
|
||||||
|
return cls._consecutive_count
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
from typing import List, Tuple, Type
|
from typing import List, Tuple, Type
|
||||||
|
|
||||||
# 导入新插件系统
|
# 导入新插件系统
|
||||||
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo, ActionActivationType
|
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
|
||||||
from src.plugin_system.base.config_types import ConfigField
|
from src.plugin_system.base.config_types import ConfigField
|
||||||
from src.config.config import global_config
|
|
||||||
|
|
||||||
# 导入依赖的系统组件
|
# 导入依赖的系统组件
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
@@ -18,7 +17,6 @@ from src.common.logger import get_logger
|
|||||||
# 导入API模块 - 标准Python包方式
|
# 导入API模块 - 标准Python包方式
|
||||||
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
||||||
from src.plugins.built_in.core_actions.emoji import EmojiAction
|
from src.plugins.built_in.core_actions.emoji import EmojiAction
|
||||||
from src.plugins.built_in.core_actions.reply import ReplyAction
|
|
||||||
|
|
||||||
logger = get_logger("core_actions")
|
logger = get_logger("core_actions")
|
||||||
|
|
||||||
@@ -52,10 +50,9 @@ class CoreActionsPlugin(BasePlugin):
|
|||||||
config_schema: dict = {
|
config_schema: dict = {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
||||||
"config_version": ConfigField(type=str, default="0.4.0", description="配置文件版本"),
|
"config_version": ConfigField(type=str, default="0.5.0", description="配置文件版本"),
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"enable_reply": ConfigField(type=bool, default=True, description="是否启用回复动作"),
|
|
||||||
"enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"),
|
"enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"),
|
||||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
|
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
|
||||||
},
|
},
|
||||||
@@ -64,24 +61,12 @@ class CoreActionsPlugin(BasePlugin):
|
|||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||||
"""返回插件包含的组件列表"""
|
"""返回插件包含的组件列表"""
|
||||||
|
|
||||||
if global_config.emoji.emoji_activate_type == "llm":
|
|
||||||
EmojiAction.random_activation_probability = 0.0
|
|
||||||
EmojiAction.focus_activation_type = ActionActivationType.LLM_JUDGE
|
|
||||||
EmojiAction.normal_activation_type = ActionActivationType.LLM_JUDGE
|
|
||||||
|
|
||||||
elif global_config.emoji.emoji_activate_type == "random":
|
|
||||||
EmojiAction.random_activation_probability = global_config.emoji.emoji_chance
|
|
||||||
EmojiAction.focus_activation_type = ActionActivationType.RANDOM
|
|
||||||
EmojiAction.normal_activation_type = ActionActivationType.RANDOM
|
|
||||||
# --- 根据配置注册组件 ---
|
# --- 根据配置注册组件 ---
|
||||||
components = []
|
components = []
|
||||||
if self.get_config("components.enable_reply", True):
|
|
||||||
components.append((ReplyAction.get_action_info(), ReplyAction))
|
|
||||||
if self.get_config("components.enable_no_reply", True):
|
if self.get_config("components.enable_no_reply", True):
|
||||||
components.append((NoReplyAction.get_action_info(), NoReplyAction))
|
components.append((NoReplyAction.get_action_info(), NoReplyAction))
|
||||||
if self.get_config("components.enable_emoji", True):
|
if self.get_config("components.enable_emoji", True):
|
||||||
components.append((EmojiAction.get_action_info(), EmojiAction))
|
components.append((EmojiAction.get_action_info(), EmojiAction))
|
||||||
|
|
||||||
# components.append((DeepReplyAction.get_action_info(), DeepReplyAction))
|
|
||||||
|
|
||||||
return components
|
return components
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
# 导入新插件系统
|
|
||||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
|
||||||
from src.config.config import global_config
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
from typing import Tuple
|
|
||||||
import asyncio
|
|
||||||
import re
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
# 导入依赖的系统组件
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
|
|
||||||
# 导入API模块 - 标准Python包方式
|
|
||||||
from src.plugin_system.apis import generator_api, message_api
|
|
||||||
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
|
||||||
from src.person_info.person_info import get_person_info_manager
|
|
||||||
from src.mais4u.mai_think import mai_thinking_manager
|
|
||||||
from src.mais4u.constant_s4u import ENABLE_S4U
|
|
||||||
|
|
||||||
logger = get_logger("reply_action")
|
|
||||||
|
|
||||||
|
|
||||||
class ReplyAction(BaseAction):
|
|
||||||
"""回复动作 - 参与聊天回复"""
|
|
||||||
|
|
||||||
# 激活设置
|
|
||||||
focus_activation_type = ActionActivationType.NEVER
|
|
||||||
normal_activation_type = ActionActivationType.NEVER
|
|
||||||
mode_enable = ChatMode.FOCUS
|
|
||||||
parallel_action = False
|
|
||||||
|
|
||||||
# 动作基本信息
|
|
||||||
action_name = "reply"
|
|
||||||
action_description = ""
|
|
||||||
|
|
||||||
# 动作参数定义
|
|
||||||
action_parameters = {}
|
|
||||||
|
|
||||||
# 动作使用场景
|
|
||||||
action_require = [""]
|
|
||||||
|
|
||||||
# 关联类型
|
|
||||||
associated_types = ["text"]
|
|
||||||
|
|
||||||
def _parse_reply_target(self, target_message: str) -> tuple:
|
|
||||||
sender = ""
|
|
||||||
target = ""
|
|
||||||
# 添加None检查,防止NoneType错误
|
|
||||||
if target_message is None:
|
|
||||||
return sender, target
|
|
||||||
if ":" in target_message or ":" in target_message:
|
|
||||||
# 使用正则表达式匹配中文或英文冒号
|
|
||||||
parts = re.split(pattern=r"[::]", string=target_message, maxsplit=1)
|
|
||||||
if len(parts) == 2:
|
|
||||||
sender = parts[0].strip()
|
|
||||||
target = parts[1].strip()
|
|
||||||
return sender, target
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
|
||||||
"""执行回复动作"""
|
|
||||||
logger.debug(f"{self.log_prefix} 决定进行回复")
|
|
||||||
start_time = self.action_data.get("loop_start_time", time.time())
|
|
||||||
|
|
||||||
user_id = self.user_id
|
|
||||||
platform = self.platform
|
|
||||||
# logger.info(f"{self.log_prefix} 用户ID: {user_id}, 平台: {platform}")
|
|
||||||
person_id = get_person_info_manager().get_person_id(platform, user_id) # type: ignore
|
|
||||||
# logger.info(f"{self.log_prefix} 人物ID: {person_id}")
|
|
||||||
person_name = get_person_info_manager().get_value_sync(person_id, "person_name")
|
|
||||||
reply_to = f"{person_name}:{self.action_message.get('processed_plain_text', '')}" # type: ignore
|
|
||||||
logger.info(f"{self.log_prefix} 决定进行回复,目标: {reply_to}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if prepared_reply := self.action_data.get("prepared_reply", ""):
|
|
||||||
reply_text = prepared_reply
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
success, reply_set, _ = await asyncio.wait_for(
|
|
||||||
generator_api.generate_reply(
|
|
||||||
extra_info="",
|
|
||||||
reply_to=reply_to,
|
|
||||||
chat_id=self.chat_id,
|
|
||||||
request_type="chat.replyer.focus",
|
|
||||||
enable_tool=global_config.tool.enable_in_focus_chat,
|
|
||||||
),
|
|
||||||
timeout=global_config.chat.thinking_timeout,
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
logger.warning(f"{self.log_prefix} 回复生成超时 ({global_config.chat.thinking_timeout}s)")
|
|
||||||
return False, "timeout"
|
|
||||||
|
|
||||||
# 检查从start_time以来的新消息数量
|
|
||||||
# 获取动作触发时间或使用默认值
|
|
||||||
current_time = time.time()
|
|
||||||
new_message_count = message_api.count_new_messages(
|
|
||||||
chat_id=self.chat_id, start_time=start_time, end_time=current_time
|
|
||||||
)
|
|
||||||
|
|
||||||
# 根据新消息数量决定是否使用reply_to
|
|
||||||
need_reply = new_message_count >= random.randint(2, 4)
|
|
||||||
if need_reply:
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.debug(
|
|
||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 构建回复文本
|
|
||||||
reply_text = ""
|
|
||||||
first_replied = False
|
|
||||||
reply_to_platform_id = f"{platform}:{user_id}"
|
|
||||||
for reply_seg in reply_set:
|
|
||||||
data = reply_seg[1]
|
|
||||||
if not first_replied:
|
|
||||||
if need_reply:
|
|
||||||
await self.send_text(
|
|
||||||
content=data, reply_to=reply_to, reply_to_platform_id=reply_to_platform_id, typing=False
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await self.send_text(content=data, reply_to_platform_id=reply_to_platform_id, typing=False)
|
|
||||||
first_replied = True
|
|
||||||
else:
|
|
||||||
await self.send_text(content=data, reply_to_platform_id=reply_to_platform_id, typing=True)
|
|
||||||
reply_text += data
|
|
||||||
|
|
||||||
# 存储动作记录
|
|
||||||
reply_text = f"你对{person_name}进行了回复:{reply_text}"
|
|
||||||
|
|
||||||
if ENABLE_S4U:
|
|
||||||
await mai_thinking_manager.get_mai_think(self.chat_id).do_think_after_response(reply_text)
|
|
||||||
|
|
||||||
await self.store_action_info(
|
|
||||||
action_build_into_prompt=False,
|
|
||||||
action_prompt_display=reply_text,
|
|
||||||
action_done=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 重置NoReplyAction的连续计数器
|
|
||||||
NoReplyAction.reset_consecutive_count()
|
|
||||||
|
|
||||||
return success, reply_text
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{self.log_prefix} 回复动作执行失败: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
return False, f"回复失败: {str(e)}"
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from src.tools.tool_can_use.base_tool import BaseTool
|
from src.plugin_system.base.base_tool import BaseTool
|
||||||
from src.chat.utils.utils import get_embedding
|
from src.chat.utils.utils import get_embedding
|
||||||
from src.common.database.database_model import Knowledges # Updated import
|
from src.common.database.database_model import Knowledges # Updated import
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
@@ -77,7 +77,7 @@ class SearchKnowledgeTool(BaseTool):
|
|||||||
Union[str, list]: 格式化的信息字符串或原始结果列表
|
Union[str, list]: 格式化的信息字符串或原始结果列表
|
||||||
"""
|
"""
|
||||||
if not query_embedding:
|
if not query_embedding:
|
||||||
return "" if not return_raw else []
|
return [] if return_raw else ""
|
||||||
|
|
||||||
similar_items = []
|
similar_items = []
|
||||||
try:
|
try:
|
||||||
@@ -115,10 +115,10 @@ class SearchKnowledgeTool(BaseTool):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"从 Peewee 数据库获取知识信息失败: {str(e)}")
|
logger.error(f"从 Peewee 数据库获取知识信息失败: {str(e)}")
|
||||||
return "" if not return_raw else []
|
return [] if return_raw else ""
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
return "" if not return_raw else []
|
return [] if return_raw else ""
|
||||||
|
|
||||||
if return_raw:
|
if return_raw:
|
||||||
# Peewee 模型实例不能直接序列化为 JSON,如果需要原始模型,调用者需要处理
|
# Peewee 模型实例不能直接序列化为 JSON,如果需要原始模型,调用者需要处理
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from src.tools.tool_can_use.base_tool import BaseTool
|
from src.plugin_system.base.base_tool import BaseTool
|
||||||
|
|
||||||
# from src.common.database import db
|
# from src.common.database import db
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"license": "GPL-v3.0-or-later",
|
"license": "GPL-v3.0-or-later",
|
||||||
"host_application": {
|
"host_application": {
|
||||||
"min_version": "0.9.0"
|
"min_version": "0.9.1"
|
||||||
},
|
},
|
||||||
"homepage_url": "https://github.com/MaiM-with-u/maibot",
|
"homepage_url": "https://github.com/MaiM-with-u/maibot",
|
||||||
"repository_url": "https://github.com/MaiM-with-u/maibot",
|
"repository_url": "https://github.com/MaiM-with-u/maibot",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from src.plugin_system import (
|
|||||||
component_manage_api,
|
component_manage_api,
|
||||||
ComponentInfo,
|
ComponentInfo,
|
||||||
ComponentType,
|
ComponentType,
|
||||||
|
send_api,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -27,8 +28,15 @@ class ManagementCommand(BaseCommand):
|
|||||||
or not self.message.message_info.user_info
|
or not self.message.message_info.user_info
|
||||||
or str(self.message.message_info.user_info.user_id) not in self.get_config("plugin.permission", []) # type: ignore
|
or str(self.message.message_info.user_info.user_id) not in self.get_config("plugin.permission", []) # type: ignore
|
||||||
):
|
):
|
||||||
await self.send_text("你没有权限使用插件管理命令")
|
await self._send_message("你没有权限使用插件管理命令")
|
||||||
return False, "没有权限", True
|
return False, "没有权限", True
|
||||||
|
if not self.message.chat_stream:
|
||||||
|
await self._send_message("无法获取聊天流信息")
|
||||||
|
return False, "无法获取聊天流信息", True
|
||||||
|
self.stream_id = self.message.chat_stream.stream_id
|
||||||
|
if not self.stream_id:
|
||||||
|
await self._send_message("无法获取聊天流信息")
|
||||||
|
return False, "无法获取聊天流信息", True
|
||||||
command_list = self.matched_groups["manage_command"].strip().split(" ")
|
command_list = self.matched_groups["manage_command"].strip().split(" ")
|
||||||
if len(command_list) == 1:
|
if len(command_list) == 1:
|
||||||
await self.show_help("all")
|
await self.show_help("all")
|
||||||
@@ -42,7 +50,7 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "help":
|
case "help":
|
||||||
await self.show_help("all")
|
await self.show_help("all")
|
||||||
case _:
|
case _:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if len(command_list) == 3:
|
if len(command_list) == 3:
|
||||||
if command_list[1] == "plugin":
|
if command_list[1] == "plugin":
|
||||||
@@ -56,7 +64,7 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "rescan":
|
case "rescan":
|
||||||
await self._rescan_plugin_dirs()
|
await self._rescan_plugin_dirs()
|
||||||
case _:
|
case _:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
elif command_list[1] == "component":
|
elif command_list[1] == "component":
|
||||||
if command_list[2] == "list":
|
if command_list[2] == "list":
|
||||||
@@ -64,10 +72,10 @@ class ManagementCommand(BaseCommand):
|
|||||||
elif command_list[2] == "help":
|
elif command_list[2] == "help":
|
||||||
await self.show_help("component")
|
await self.show_help("component")
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if len(command_list) == 4:
|
if len(command_list) == 4:
|
||||||
if command_list[1] == "plugin":
|
if command_list[1] == "plugin":
|
||||||
@@ -81,28 +89,28 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "add_dir":
|
case "add_dir":
|
||||||
await self._add_dir(command_list[3])
|
await self._add_dir(command_list[3])
|
||||||
case _:
|
case _:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
elif command_list[1] == "component":
|
elif command_list[1] == "component":
|
||||||
if command_list[2] != "list":
|
if command_list[2] != "list":
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if command_list[3] == "enabled":
|
if command_list[3] == "enabled":
|
||||||
await self._list_enabled_components()
|
await self._list_enabled_components()
|
||||||
elif command_list[3] == "disabled":
|
elif command_list[3] == "disabled":
|
||||||
await self._list_disabled_components()
|
await self._list_disabled_components()
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if len(command_list) == 5:
|
if len(command_list) == 5:
|
||||||
if command_list[1] != "component":
|
if command_list[1] != "component":
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if command_list[2] != "list":
|
if command_list[2] != "list":
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if command_list[3] == "enabled":
|
if command_list[3] == "enabled":
|
||||||
await self._list_enabled_components(target_type=command_list[4])
|
await self._list_enabled_components(target_type=command_list[4])
|
||||||
@@ -111,11 +119,11 @@ class ManagementCommand(BaseCommand):
|
|||||||
elif command_list[3] == "type":
|
elif command_list[3] == "type":
|
||||||
await self._list_registered_components_by_type(command_list[4])
|
await self._list_registered_components_by_type(command_list[4])
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if len(command_list) == 6:
|
if len(command_list) == 6:
|
||||||
if command_list[1] != "component":
|
if command_list[1] != "component":
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
if command_list[2] == "enable":
|
if command_list[2] == "enable":
|
||||||
if command_list[3] == "global":
|
if command_list[3] == "global":
|
||||||
@@ -123,7 +131,7 @@ class ManagementCommand(BaseCommand):
|
|||||||
elif command_list[3] == "local":
|
elif command_list[3] == "local":
|
||||||
await self._locally_enable_component(command_list[4], command_list[5])
|
await self._locally_enable_component(command_list[4], command_list[5])
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
elif command_list[2] == "disable":
|
elif command_list[2] == "disable":
|
||||||
if command_list[3] == "global":
|
if command_list[3] == "global":
|
||||||
@@ -131,10 +139,10 @@ class ManagementCommand(BaseCommand):
|
|||||||
elif command_list[3] == "local":
|
elif command_list[3] == "local":
|
||||||
await self._locally_disable_component(command_list[4], command_list[5])
|
await self._locally_disable_component(command_list[4], command_list[5])
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
else:
|
else:
|
||||||
await self.send_text("插件管理命令不合法")
|
await self._send_message("插件管理命令不合法")
|
||||||
return False, "命令不合法", True
|
return False, "命令不合法", True
|
||||||
|
|
||||||
return True, "命令执行完成", True
|
return True, "命令执行完成", True
|
||||||
@@ -180,51 +188,51 @@ class ManagementCommand(BaseCommand):
|
|||||||
)
|
)
|
||||||
case _:
|
case _:
|
||||||
return
|
return
|
||||||
await self.send_text(help_msg)
|
await self._send_message(help_msg)
|
||||||
|
|
||||||
async def _list_loaded_plugins(self):
|
async def _list_loaded_plugins(self):
|
||||||
plugins = plugin_manage_api.list_loaded_plugins()
|
plugins = plugin_manage_api.list_loaded_plugins()
|
||||||
await self.send_text(f"已加载的插件: {', '.join(plugins)}")
|
await self._send_message(f"已加载的插件: {', '.join(plugins)}")
|
||||||
|
|
||||||
async def _list_registered_plugins(self):
|
async def _list_registered_plugins(self):
|
||||||
plugins = plugin_manage_api.list_registered_plugins()
|
plugins = plugin_manage_api.list_registered_plugins()
|
||||||
await self.send_text(f"已注册的插件: {', '.join(plugins)}")
|
await self._send_message(f"已注册的插件: {', '.join(plugins)}")
|
||||||
|
|
||||||
async def _rescan_plugin_dirs(self):
|
async def _rescan_plugin_dirs(self):
|
||||||
plugin_manage_api.rescan_plugin_directory()
|
plugin_manage_api.rescan_plugin_directory()
|
||||||
await self.send_text("插件目录重新扫描执行中")
|
await self._send_message("插件目录重新扫描执行中")
|
||||||
|
|
||||||
async def _load_plugin(self, plugin_name: str):
|
async def _load_plugin(self, plugin_name: str):
|
||||||
success, count = plugin_manage_api.load_plugin(plugin_name)
|
success, count = plugin_manage_api.load_plugin(plugin_name)
|
||||||
if success:
|
if success:
|
||||||
await self.send_text(f"插件加载成功: {plugin_name}")
|
await self._send_message(f"插件加载成功: {plugin_name}")
|
||||||
else:
|
else:
|
||||||
if count == 0:
|
if count == 0:
|
||||||
await self.send_text(f"插件{plugin_name}为禁用状态")
|
await self._send_message(f"插件{plugin_name}为禁用状态")
|
||||||
await self.send_text(f"插件加载失败: {plugin_name}")
|
await self._send_message(f"插件加载失败: {plugin_name}")
|
||||||
|
|
||||||
async def _unload_plugin(self, plugin_name: str):
|
async def _unload_plugin(self, plugin_name: str):
|
||||||
success = await plugin_manage_api.remove_plugin(plugin_name)
|
success = await plugin_manage_api.remove_plugin(plugin_name)
|
||||||
if success:
|
if success:
|
||||||
await self.send_text(f"插件卸载成功: {plugin_name}")
|
await self._send_message(f"插件卸载成功: {plugin_name}")
|
||||||
else:
|
else:
|
||||||
await self.send_text(f"插件卸载失败: {plugin_name}")
|
await self._send_message(f"插件卸载失败: {plugin_name}")
|
||||||
|
|
||||||
async def _reload_plugin(self, plugin_name: str):
|
async def _reload_plugin(self, plugin_name: str):
|
||||||
success = await plugin_manage_api.reload_plugin(plugin_name)
|
success = await plugin_manage_api.reload_plugin(plugin_name)
|
||||||
if success:
|
if success:
|
||||||
await self.send_text(f"插件重新加载成功: {plugin_name}")
|
await self._send_message(f"插件重新加载成功: {plugin_name}")
|
||||||
else:
|
else:
|
||||||
await self.send_text(f"插件重新加载失败: {plugin_name}")
|
await self._send_message(f"插件重新加载失败: {plugin_name}")
|
||||||
|
|
||||||
async def _add_dir(self, dir_path: str):
|
async def _add_dir(self, dir_path: str):
|
||||||
await self.send_text(f"正在添加插件目录: {dir_path}")
|
await self._send_message(f"正在添加插件目录: {dir_path}")
|
||||||
success = plugin_manage_api.add_plugin_directory(dir_path)
|
success = plugin_manage_api.add_plugin_directory(dir_path)
|
||||||
await asyncio.sleep(0.5) # 防止乱序发送
|
await asyncio.sleep(0.5) # 防止乱序发送
|
||||||
if success:
|
if success:
|
||||||
await self.send_text(f"插件目录添加成功: {dir_path}")
|
await self._send_message(f"插件目录添加成功: {dir_path}")
|
||||||
else:
|
else:
|
||||||
await self.send_text(f"插件目录添加失败: {dir_path}")
|
await self._send_message(f"插件目录添加失败: {dir_path}")
|
||||||
|
|
||||||
def _fetch_all_registered_components(self) -> List[ComponentInfo]:
|
def _fetch_all_registered_components(self) -> List[ComponentInfo]:
|
||||||
all_plugin_info = component_manage_api.get_all_plugin_info()
|
all_plugin_info = component_manage_api.get_all_plugin_info()
|
||||||
@@ -255,29 +263,29 @@ class ManagementCommand(BaseCommand):
|
|||||||
async def _list_all_registered_components(self):
|
async def _list_all_registered_components(self):
|
||||||
components_info = self._fetch_all_registered_components()
|
components_info = self._fetch_all_registered_components()
|
||||||
if not components_info:
|
if not components_info:
|
||||||
await self.send_text("没有注册的组件")
|
await self._send_message("没有注册的组件")
|
||||||
return
|
return
|
||||||
|
|
||||||
all_components_str = ", ".join(
|
all_components_str = ", ".join(
|
||||||
f"{component.name} ({component.component_type})" for component in components_info
|
f"{component.name} ({component.component_type})" for component in components_info
|
||||||
)
|
)
|
||||||
await self.send_text(f"已注册的组件: {all_components_str}")
|
await self._send_message(f"已注册的组件: {all_components_str}")
|
||||||
|
|
||||||
async def _list_enabled_components(self, target_type: str = "global"):
|
async def _list_enabled_components(self, target_type: str = "global"):
|
||||||
components_info = self._fetch_all_registered_components()
|
components_info = self._fetch_all_registered_components()
|
||||||
if not components_info:
|
if not components_info:
|
||||||
await self.send_text("没有注册的组件")
|
await self._send_message("没有注册的组件")
|
||||||
return
|
return
|
||||||
|
|
||||||
if target_type == "global":
|
if target_type == "global":
|
||||||
enabled_components = [component for component in components_info if component.enabled]
|
enabled_components = [component for component in components_info if component.enabled]
|
||||||
if not enabled_components:
|
if not enabled_components:
|
||||||
await self.send_text("没有满足条件的已启用全局组件")
|
await self._send_message("没有满足条件的已启用全局组件")
|
||||||
return
|
return
|
||||||
enabled_components_str = ", ".join(
|
enabled_components_str = ", ".join(
|
||||||
f"{component.name} ({component.component_type})" for component in enabled_components
|
f"{component.name} ({component.component_type})" for component in enabled_components
|
||||||
)
|
)
|
||||||
await self.send_text(f"满足条件的已启用全局组件: {enabled_components_str}")
|
await self._send_message(f"满足条件的已启用全局组件: {enabled_components_str}")
|
||||||
elif target_type == "local":
|
elif target_type == "local":
|
||||||
locally_disabled_components = self._fetch_locally_disabled_components()
|
locally_disabled_components = self._fetch_locally_disabled_components()
|
||||||
enabled_components = [
|
enabled_components = [
|
||||||
@@ -286,28 +294,28 @@ class ManagementCommand(BaseCommand):
|
|||||||
if (component.name not in locally_disabled_components and component.enabled)
|
if (component.name not in locally_disabled_components and component.enabled)
|
||||||
]
|
]
|
||||||
if not enabled_components:
|
if not enabled_components:
|
||||||
await self.send_text("本聊天没有满足条件的已启用组件")
|
await self._send_message("本聊天没有满足条件的已启用组件")
|
||||||
return
|
return
|
||||||
enabled_components_str = ", ".join(
|
enabled_components_str = ", ".join(
|
||||||
f"{component.name} ({component.component_type})" for component in enabled_components
|
f"{component.name} ({component.component_type})" for component in enabled_components
|
||||||
)
|
)
|
||||||
await self.send_text(f"本聊天满足条件的已启用组件: {enabled_components_str}")
|
await self._send_message(f"本聊天满足条件的已启用组件: {enabled_components_str}")
|
||||||
|
|
||||||
async def _list_disabled_components(self, target_type: str = "global"):
|
async def _list_disabled_components(self, target_type: str = "global"):
|
||||||
components_info = self._fetch_all_registered_components()
|
components_info = self._fetch_all_registered_components()
|
||||||
if not components_info:
|
if not components_info:
|
||||||
await self.send_text("没有注册的组件")
|
await self._send_message("没有注册的组件")
|
||||||
return
|
return
|
||||||
|
|
||||||
if target_type == "global":
|
if target_type == "global":
|
||||||
disabled_components = [component for component in components_info if not component.enabled]
|
disabled_components = [component for component in components_info if not component.enabled]
|
||||||
if not disabled_components:
|
if not disabled_components:
|
||||||
await self.send_text("没有满足条件的已禁用全局组件")
|
await self._send_message("没有满足条件的已禁用全局组件")
|
||||||
return
|
return
|
||||||
disabled_components_str = ", ".join(
|
disabled_components_str = ", ".join(
|
||||||
f"{component.name} ({component.component_type})" for component in disabled_components
|
f"{component.name} ({component.component_type})" for component in disabled_components
|
||||||
)
|
)
|
||||||
await self.send_text(f"满足条件的已禁用全局组件: {disabled_components_str}")
|
await self._send_message(f"满足条件的已禁用全局组件: {disabled_components_str}")
|
||||||
elif target_type == "local":
|
elif target_type == "local":
|
||||||
locally_disabled_components = self._fetch_locally_disabled_components()
|
locally_disabled_components = self._fetch_locally_disabled_components()
|
||||||
disabled_components = [
|
disabled_components = [
|
||||||
@@ -316,12 +324,12 @@ class ManagementCommand(BaseCommand):
|
|||||||
if (component.name in locally_disabled_components or not component.enabled)
|
if (component.name in locally_disabled_components or not component.enabled)
|
||||||
]
|
]
|
||||||
if not disabled_components:
|
if not disabled_components:
|
||||||
await self.send_text("本聊天没有满足条件的已禁用组件")
|
await self._send_message("本聊天没有满足条件的已禁用组件")
|
||||||
return
|
return
|
||||||
disabled_components_str = ", ".join(
|
disabled_components_str = ", ".join(
|
||||||
f"{component.name} ({component.component_type})" for component in disabled_components
|
f"{component.name} ({component.component_type})" for component in disabled_components
|
||||||
)
|
)
|
||||||
await self.send_text(f"本聊天满足条件的已禁用组件: {disabled_components_str}")
|
await self._send_message(f"本聊天满足条件的已禁用组件: {disabled_components_str}")
|
||||||
|
|
||||||
async def _list_registered_components_by_type(self, target_type: str):
|
async def _list_registered_components_by_type(self, target_type: str):
|
||||||
match target_type:
|
match target_type:
|
||||||
@@ -332,18 +340,18 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "event_handler":
|
case "event_handler":
|
||||||
component_type = ComponentType.EVENT_HANDLER
|
component_type = ComponentType.EVENT_HANDLER
|
||||||
case _:
|
case _:
|
||||||
await self.send_text(f"未知组件类型: {target_type}")
|
await self._send_message(f"未知组件类型: {target_type}")
|
||||||
return
|
return
|
||||||
|
|
||||||
components_info = component_manage_api.get_components_info_by_type(component_type)
|
components_info = component_manage_api.get_components_info_by_type(component_type)
|
||||||
if not components_info:
|
if not components_info:
|
||||||
await self.send_text(f"没有注册的 {target_type} 组件")
|
await self._send_message(f"没有注册的 {target_type} 组件")
|
||||||
return
|
return
|
||||||
|
|
||||||
components_str = ", ".join(
|
components_str = ", ".join(
|
||||||
f"{name} ({component.component_type})" for name, component in components_info.items()
|
f"{name} ({component.component_type})" for name, component in components_info.items()
|
||||||
)
|
)
|
||||||
await self.send_text(f"注册的 {target_type} 组件: {components_str}")
|
await self._send_message(f"注册的 {target_type} 组件: {components_str}")
|
||||||
|
|
||||||
async def _globally_enable_component(self, component_name: str, component_type: str):
|
async def _globally_enable_component(self, component_name: str, component_type: str):
|
||||||
match component_type:
|
match component_type:
|
||||||
@@ -354,12 +362,12 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "event_handler":
|
case "event_handler":
|
||||||
target_component_type = ComponentType.EVENT_HANDLER
|
target_component_type = ComponentType.EVENT_HANDLER
|
||||||
case _:
|
case _:
|
||||||
await self.send_text(f"未知组件类型: {component_type}")
|
await self._send_message(f"未知组件类型: {component_type}")
|
||||||
return
|
return
|
||||||
if component_manage_api.globally_enable_component(component_name, target_component_type):
|
if component_manage_api.globally_enable_component(component_name, target_component_type):
|
||||||
await self.send_text(f"全局启用组件成功: {component_name}")
|
await self._send_message(f"全局启用组件成功: {component_name}")
|
||||||
else:
|
else:
|
||||||
await self.send_text(f"全局启用组件失败: {component_name}")
|
await self._send_message(f"全局启用组件失败: {component_name}")
|
||||||
|
|
||||||
async def _globally_disable_component(self, component_name: str, component_type: str):
|
async def _globally_disable_component(self, component_name: str, component_type: str):
|
||||||
match component_type:
|
match component_type:
|
||||||
@@ -370,13 +378,13 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "event_handler":
|
case "event_handler":
|
||||||
target_component_type = ComponentType.EVENT_HANDLER
|
target_component_type = ComponentType.EVENT_HANDLER
|
||||||
case _:
|
case _:
|
||||||
await self.send_text(f"未知组件类型: {component_type}")
|
await self._send_message(f"未知组件类型: {component_type}")
|
||||||
return
|
return
|
||||||
success = await component_manage_api.globally_disable_component(component_name, target_component_type)
|
success = await component_manage_api.globally_disable_component(component_name, target_component_type)
|
||||||
if success:
|
if success:
|
||||||
await self.send_text(f"全局禁用组件成功: {component_name}")
|
await self._send_message(f"全局禁用组件成功: {component_name}")
|
||||||
else:
|
else:
|
||||||
await self.send_text(f"全局禁用组件失败: {component_name}")
|
await self._send_message(f"全局禁用组件失败: {component_name}")
|
||||||
|
|
||||||
async def _locally_enable_component(self, component_name: str, component_type: str):
|
async def _locally_enable_component(self, component_name: str, component_type: str):
|
||||||
match component_type:
|
match component_type:
|
||||||
@@ -387,16 +395,16 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "event_handler":
|
case "event_handler":
|
||||||
target_component_type = ComponentType.EVENT_HANDLER
|
target_component_type = ComponentType.EVENT_HANDLER
|
||||||
case _:
|
case _:
|
||||||
await self.send_text(f"未知组件类型: {component_type}")
|
await self._send_message(f"未知组件类型: {component_type}")
|
||||||
return
|
return
|
||||||
if component_manage_api.locally_enable_component(
|
if component_manage_api.locally_enable_component(
|
||||||
component_name,
|
component_name,
|
||||||
target_component_type,
|
target_component_type,
|
||||||
self.message.chat_stream.stream_id,
|
self.message.chat_stream.stream_id,
|
||||||
):
|
):
|
||||||
await self.send_text(f"本地启用组件成功: {component_name}")
|
await self._send_message(f"本地启用组件成功: {component_name}")
|
||||||
else:
|
else:
|
||||||
await self.send_text(f"本地启用组件失败: {component_name}")
|
await self._send_message(f"本地启用组件失败: {component_name}")
|
||||||
|
|
||||||
async def _locally_disable_component(self, component_name: str, component_type: str):
|
async def _locally_disable_component(self, component_name: str, component_type: str):
|
||||||
match component_type:
|
match component_type:
|
||||||
@@ -407,34 +415,40 @@ class ManagementCommand(BaseCommand):
|
|||||||
case "event_handler":
|
case "event_handler":
|
||||||
target_component_type = ComponentType.EVENT_HANDLER
|
target_component_type = ComponentType.EVENT_HANDLER
|
||||||
case _:
|
case _:
|
||||||
await self.send_text(f"未知组件类型: {component_type}")
|
await self._send_message(f"未知组件类型: {component_type}")
|
||||||
return
|
return
|
||||||
if component_manage_api.locally_disable_component(
|
if component_manage_api.locally_disable_component(
|
||||||
component_name,
|
component_name,
|
||||||
target_component_type,
|
target_component_type,
|
||||||
self.message.chat_stream.stream_id,
|
self.message.chat_stream.stream_id,
|
||||||
):
|
):
|
||||||
await self.send_text(f"本地禁用组件成功: {component_name}")
|
await self._send_message(f"本地禁用组件成功: {component_name}")
|
||||||
else:
|
else:
|
||||||
await self.send_text(f"本地禁用组件失败: {component_name}")
|
await self._send_message(f"本地禁用组件失败: {component_name}")
|
||||||
|
|
||||||
|
async def _send_message(self, message: str):
|
||||||
|
await send_api.text_to_stream(message, self.stream_id, typing=False, storage_message=False)
|
||||||
|
|
||||||
|
|
||||||
@register_plugin
|
@register_plugin
|
||||||
class PluginManagementPlugin(BasePlugin):
|
class PluginManagementPlugin(BasePlugin):
|
||||||
plugin_name: str = "plugin_management_plugin"
|
plugin_name: str = "plugin_management_plugin"
|
||||||
enable_plugin: bool = True
|
enable_plugin: bool = False
|
||||||
dependencies: list[str] = []
|
dependencies: list[str] = []
|
||||||
python_dependencies: list[str] = []
|
python_dependencies: list[str] = []
|
||||||
config_file_name: str = "config.toml"
|
config_file_name: str = "config.toml"
|
||||||
config_schema: dict = {
|
config_schema: dict = {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"enable": ConfigField(bool, default=True, description="是否启用插件"),
|
"enabled": ConfigField(bool, default=False, description="是否启用插件"),
|
||||||
"permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表"),
|
"config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"),
|
||||||
|
"permission": ConfigField(
|
||||||
|
list, default=[], description="有权限使用插件管理命令的用户列表,请填写字符串形式的用户ID"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[CommandInfo, Type[BaseCommand]]]:
|
def get_plugin_components(self) -> List[Tuple[CommandInfo, Type[BaseCommand]]]:
|
||||||
components = []
|
components = []
|
||||||
if self.get_config("plugin.enable", True):
|
if self.get_config("plugin.enabled", True):
|
||||||
components.append((ManagementCommand.get_command_info(), ManagementCommand))
|
components.append((ManagementCommand.get_command_info(), ManagementCommand))
|
||||||
return components
|
return components
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
from src.tools.tool_can_use.base_tool import (
|
|
||||||
BaseTool,
|
|
||||||
register_tool,
|
|
||||||
discover_tools,
|
|
||||||
get_all_tool_definitions,
|
|
||||||
get_tool_instance,
|
|
||||||
TOOL_REGISTRY,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"BaseTool",
|
|
||||||
"register_tool",
|
|
||||||
"discover_tools",
|
|
||||||
"get_all_tool_definitions",
|
|
||||||
"get_tool_instance",
|
|
||||||
"TOOL_REGISTRY",
|
|
||||||
]
|
|
||||||
|
|
||||||
# 自动发现并注册工具
|
|
||||||
discover_tools()
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
from typing import List, Any, Optional, Type
|
|
||||||
import inspect
|
|
||||||
import importlib
|
|
||||||
import pkgutil
|
|
||||||
import os
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
from rich.traceback import install
|
|
||||||
|
|
||||||
install(extra_lines=3)
|
|
||||||
|
|
||||||
logger = get_logger("base_tool")
|
|
||||||
|
|
||||||
# 工具注册表
|
|
||||||
TOOL_REGISTRY = {}
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTool:
|
|
||||||
"""所有工具的基类"""
|
|
||||||
|
|
||||||
# 工具名称,子类必须重写
|
|
||||||
name = None
|
|
||||||
# 工具描述,子类必须重写
|
|
||||||
description = None
|
|
||||||
# 工具参数定义,子类必须重写
|
|
||||||
parameters = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_tool_definition(cls) -> dict[str, Any]:
|
|
||||||
"""获取工具定义,用于LLM工具调用
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 工具定义字典
|
|
||||||
"""
|
|
||||||
if not cls.name or not cls.description or not cls.parameters:
|
|
||||||
raise NotImplementedError(f"工具类 {cls.__name__} 必须定义 name, description 和 parameters 属性")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"type": "function",
|
|
||||||
"function": {"name": cls.name, "description": cls.description, "parameters": cls.parameters},
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
|
|
||||||
"""执行工具函数
|
|
||||||
|
|
||||||
Args:
|
|
||||||
function_args: 工具调用参数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 工具执行结果
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("子类必须实现execute方法")
|
|
||||||
|
|
||||||
|
|
||||||
def register_tool(tool_class: Type[BaseTool]):
|
|
||||||
"""注册工具到全局注册表
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tool_class: 工具类
|
|
||||||
"""
|
|
||||||
if not issubclass(tool_class, BaseTool):
|
|
||||||
raise TypeError(f"{tool_class.__name__} 不是 BaseTool 的子类")
|
|
||||||
|
|
||||||
tool_name = tool_class.name
|
|
||||||
if not tool_name:
|
|
||||||
raise ValueError(f"工具类 {tool_class.__name__} 没有定义 name 属性")
|
|
||||||
|
|
||||||
TOOL_REGISTRY[tool_name] = tool_class
|
|
||||||
logger.info(f"已注册: {tool_name}")
|
|
||||||
|
|
||||||
|
|
||||||
def discover_tools():
|
|
||||||
"""自动发现并注册tool_can_use目录下的所有工具"""
|
|
||||||
# 获取当前目录路径
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
package_name = os.path.basename(current_dir)
|
|
||||||
|
|
||||||
# 遍历包中的所有模块
|
|
||||||
for _, module_name, _ in pkgutil.iter_modules([current_dir]):
|
|
||||||
# 跳过当前模块和__pycache__
|
|
||||||
if module_name == "base_tool" or module_name.startswith("__"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 导入模块
|
|
||||||
module = importlib.import_module(f"src.tools.{package_name}.{module_name}")
|
|
||||||
|
|
||||||
# 查找模块中的工具类
|
|
||||||
for _, obj in inspect.getmembers(module):
|
|
||||||
if inspect.isclass(obj) and issubclass(obj, BaseTool) and obj != BaseTool:
|
|
||||||
register_tool(obj)
|
|
||||||
|
|
||||||
logger.info(f"工具发现完成,共注册 {len(TOOL_REGISTRY)} 个工具")
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_tool_definitions() -> List[dict[str, Any]]:
|
|
||||||
"""获取所有已注册工具的定义
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[dict]: 工具定义列表
|
|
||||||
"""
|
|
||||||
return [tool_class().get_tool_definition() for tool_class in TOOL_REGISTRY.values()]
|
|
||||||
|
|
||||||
|
|
||||||
def get_tool_instance(tool_name: str) -> Optional[BaseTool]:
|
|
||||||
"""获取指定名称的工具实例
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tool_name: 工具名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Optional[BaseTool]: 工具实例,如果找不到则返回None
|
|
||||||
"""
|
|
||||||
tool_class = TOOL_REGISTRY.get(tool_name)
|
|
||||||
if not tool_class:
|
|
||||||
return None
|
|
||||||
return tool_class()
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
from src.tools.tool_can_use.base_tool import BaseTool
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
logger = get_logger("compare_numbers_tool")
|
|
||||||
|
|
||||||
|
|
||||||
class CompareNumbersTool(BaseTool):
|
|
||||||
"""比较两个数大小的工具"""
|
|
||||||
|
|
||||||
name = "compare_numbers"
|
|
||||||
description = "使用工具 比较两个数的大小,返回较大的数"
|
|
||||||
parameters = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"num1": {"type": "number", "description": "第一个数字"},
|
|
||||||
"num2": {"type": "number", "description": "第二个数字"},
|
|
||||||
},
|
|
||||||
"required": ["num1", "num2"],
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, function_args: dict[str, Any]) -> dict[str, Any]:
|
|
||||||
"""执行比较两个数的大小
|
|
||||||
|
|
||||||
Args:
|
|
||||||
function_args: 工具参数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 工具执行结果
|
|
||||||
"""
|
|
||||||
num1: int | float = function_args.get("num1") # type: ignore
|
|
||||||
num2: int | float = function_args.get("num2") # type: ignore
|
|
||||||
|
|
||||||
try:
|
|
||||||
if num1 > num2:
|
|
||||||
result = f"{num1} 大于 {num2}"
|
|
||||||
elif num1 < num2:
|
|
||||||
result = f"{num1} 小于 {num2}"
|
|
||||||
else:
|
|
||||||
result = f"{num1} 等于 {num2}"
|
|
||||||
|
|
||||||
return {"type": "comparison_result", "id": f"{num1}_vs_{num2}", "content": result}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"比较数字失败: {str(e)}")
|
|
||||||
return {"type": "info", "id": f"{num1}_vs_{num2}", "content": f"比较数字失败,炸了: {str(e)}"}
|
|
||||||
|
|
||||||
|
|
||||||
# 注册工具
|
|
||||||
# register_tool(CompareNumbersTool)
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
from src.tools.tool_can_use.base_tool import BaseTool
|
|
||||||
from src.person_info.person_info import get_person_info_manager
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("rename_person_tool")
|
|
||||||
|
|
||||||
|
|
||||||
class RenamePersonTool(BaseTool):
|
|
||||||
name = "rename_person"
|
|
||||||
description = (
|
|
||||||
"这个工具可以改变用户的昵称。你可以选择改变对他人的称呼。你想给人改名,叫别人别的称呼,需要调用这个工具。"
|
|
||||||
)
|
|
||||||
parameters = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"person_name": {"type": "string", "description": "需要重新取名的用户的当前昵称"},
|
|
||||||
"message_content": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "当前的聊天内容或特定要求,用于提供取名建议的上下文,尽可能详细。",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["person_name"],
|
|
||||||
}
|
|
||||||
|
|
||||||
async def execute(self, function_args: dict, message_txt=""):
|
|
||||||
"""
|
|
||||||
执行取名工具逻辑
|
|
||||||
|
|
||||||
Args:
|
|
||||||
function_args (dict): 包含 'person_name' 和可选 'message_content' 的字典
|
|
||||||
message_txt (str): 原始消息文本 (这里未使用,因为 message_content 更明确)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 包含执行结果的字典
|
|
||||||
"""
|
|
||||||
person_name_to_find = function_args.get("person_name")
|
|
||||||
request_context = function_args.get("message_content", "") # 如果没有提供,则为空字符串
|
|
||||||
|
|
||||||
if not person_name_to_find:
|
|
||||||
return {"name": self.name, "content": "错误:必须提供需要重命名的用户昵称 (person_name)。"}
|
|
||||||
person_info_manager = get_person_info_manager()
|
|
||||||
try:
|
|
||||||
# 1. 根据昵称查找用户信息
|
|
||||||
logger.debug(f"尝试根据昵称 '{person_name_to_find}' 查找用户...")
|
|
||||||
person_info = await person_info_manager.get_person_info_by_name(person_name_to_find)
|
|
||||||
|
|
||||||
if not person_info:
|
|
||||||
logger.info(f"未找到昵称为 '{person_name_to_find}' 的用户。")
|
|
||||||
return {
|
|
||||||
"name": self.name,
|
|
||||||
"content": f"找不到昵称为 '{person_name_to_find}' 的用户。请确保输入的是我之前为该用户取的昵称。",
|
|
||||||
}
|
|
||||||
|
|
||||||
person_id = person_info.get("person_id")
|
|
||||||
user_nickname = person_info.get("nickname") # 这是用户原始昵称
|
|
||||||
user_cardname = person_info.get("user_cardname")
|
|
||||||
user_avatar = person_info.get("user_avatar")
|
|
||||||
|
|
||||||
if not person_id:
|
|
||||||
logger.error(f"找到了用户 '{person_name_to_find}' 但无法获取 person_id")
|
|
||||||
return {"name": self.name, "content": f"找到了用户 '{person_name_to_find}' 但获取内部ID时出错。"}
|
|
||||||
|
|
||||||
# 2. 调用 qv_person_name 进行取名
|
|
||||||
logger.debug(
|
|
||||||
f"为用户 {person_id} (原昵称: {person_name_to_find}) 调用 qv_person_name,请求上下文: '{request_context}'"
|
|
||||||
)
|
|
||||||
result = await person_info_manager.qv_person_name(
|
|
||||||
person_id=person_id,
|
|
||||||
user_nickname=user_nickname, # type: ignore
|
|
||||||
user_cardname=user_cardname, # type: ignore
|
|
||||||
user_avatar=user_avatar, # type: ignore
|
|
||||||
request=request_context,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3. 处理结果
|
|
||||||
if result and result.get("nickname"):
|
|
||||||
new_name = result["nickname"]
|
|
||||||
# reason = result.get("reason", "未提供理由")
|
|
||||||
logger.info(f"成功为用户 {person_id} 取了新昵称: {new_name}")
|
|
||||||
|
|
||||||
content = f"已成功将用户 {person_name_to_find} 的备注名更新为 {new_name}"
|
|
||||||
logger.info(content)
|
|
||||||
return {"type": "info", "id": f"rename_success_{time.time()}", "content": content}
|
|
||||||
else:
|
|
||||||
logger.warning(f"为用户 {person_id} 调用 qv_person_name 后未能成功获取新昵称。")
|
|
||||||
# 尝试从内存中获取可能已经更新的名字
|
|
||||||
current_name = await person_info_manager.get_value(person_id, "person_name")
|
|
||||||
if current_name and current_name != person_name_to_find:
|
|
||||||
return {
|
|
||||||
"name": self.name,
|
|
||||||
"content": f"尝试取新昵称时遇到一点小问题,但我已经将 '{person_name_to_find}' 的昵称更新为 '{current_name}' 了。",
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
"name": self.name,
|
|
||||||
"content": f"尝试为 '{person_name_to_find}' 取新昵称时遇到了问题,未能成功生成。可能需要稍后再试。",
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"重命名失败: {str(e)}"
|
|
||||||
logger.error(error_msg, exc_info=True)
|
|
||||||
return {"type": "info_error", "id": f"rename_error_{time.time()}", "content": error_msg}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import json
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
from src.tools.tool_can_use import get_all_tool_definitions, get_tool_instance
|
|
||||||
|
|
||||||
logger = get_logger("tool_use")
|
|
||||||
|
|
||||||
|
|
||||||
class ToolUser:
|
|
||||||
@staticmethod
|
|
||||||
def _define_tools():
|
|
||||||
"""获取所有已注册工具的定义
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: 工具定义列表
|
|
||||||
"""
|
|
||||||
return get_all_tool_definitions()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _execute_tool_call(tool_call):
|
|
||||||
"""执行特定的工具调用
|
|
||||||
|
|
||||||
Args:
|
|
||||||
tool_call: 工具调用对象
|
|
||||||
message_txt: 原始消息文本
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 工具调用结果
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
function_name = tool_call["function"]["name"]
|
|
||||||
function_args = json.loads(tool_call["function"]["arguments"])
|
|
||||||
|
|
||||||
# 获取对应工具实例
|
|
||||||
tool_instance = get_tool_instance(function_name)
|
|
||||||
if not tool_instance:
|
|
||||||
logger.warning(f"未知工具名称: {function_name}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 执行工具
|
|
||||||
result = await tool_instance.execute(function_args)
|
|
||||||
if result:
|
|
||||||
# 直接使用 function_name 作为 tool_type
|
|
||||||
tool_type = function_name
|
|
||||||
|
|
||||||
return {
|
|
||||||
"tool_call_id": tool_call["id"],
|
|
||||||
"role": "tool",
|
|
||||||
"name": function_name,
|
|
||||||
"type": tool_type,
|
|
||||||
"content": result["content"],
|
|
||||||
}
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"执行工具调用时发生错误: {str(e)}")
|
|
||||||
return None
|
|
||||||
@@ -52,26 +52,26 @@ relation_frequency = 1 # 关系频率,麦麦构建关系的频率
|
|||||||
|
|
||||||
[chat] #麦麦的聊天通用设置
|
[chat] #麦麦的聊天通用设置
|
||||||
focus_value = 1
|
focus_value = 1
|
||||||
# 麦麦的专注思考能力,越低越容易专注,消耗token也越多
|
# 麦麦的专注思考能力,越高越容易专注,可能消耗更多token
|
||||||
# 专注时能更好把握发言时机,能够进行持久的连续对话
|
# 专注时能更好把握发言时机,能够进行持久的连续对话
|
||||||
|
|
||||||
|
willing_amplifier = 1 # 麦麦回复意愿
|
||||||
|
|
||||||
max_context_size = 25 # 上下文长度
|
max_context_size = 25 # 上下文长度
|
||||||
thinking_timeout = 20 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)
|
thinking_timeout = 40 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)
|
||||||
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
|
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
|
||||||
|
|
||||||
mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
|
mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
|
||||||
at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复
|
at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复
|
||||||
|
|
||||||
use_s4u_prompt_mode = true # 是否使用 s4u 对话构建模式,该模式会更好的把握当前对话对象的对话内容,但是对群聊整理理解能力较差(测试功能!!可能有未知问题!!)
|
|
||||||
|
|
||||||
|
|
||||||
talk_frequency = 1 # 麦麦回复频率,越高,麦麦回复越频繁
|
talk_frequency = 1 # 麦麦回复频率,越高,麦麦回复越频繁
|
||||||
|
|
||||||
time_based_talk_frequency = ["8:00,1", "12:00,1.5", "18:00,2", "01:00,0.5"]
|
time_based_talk_frequency = ["8:00,1", "12:00,1.2", "18:00,1.5", "01:00,0.6"]
|
||||||
# 基于时段的回复频率配置(可选)
|
# 基于时段的回复频率配置(可选)
|
||||||
# 格式:time_based_talk_frequency = ["HH:MM,frequency", ...]
|
# 格式:time_based_talk_frequency = ["HH:MM,frequency", ...]
|
||||||
# 示例:
|
# 示例:
|
||||||
# time_based_talk_frequency = ["8:00,1", "12:00,2", "18:00,1.5", "00:00,0.5"]
|
# time_based_talk_frequency = ["8:00,1", "12:00,1.2", "18:00,1.5", "00:00,0.6"]
|
||||||
# 说明:表示从该时间开始使用该频率,直到下一个时间点
|
# 说明:表示从该时间开始使用该频率,直到下一个时间点
|
||||||
# 注意:如果没有配置,则使用上面的默认 talk_frequency 值
|
# 注意:如果没有配置,则使用上面的默认 talk_frequency 值
|
||||||
|
|
||||||
@@ -105,11 +105,9 @@ ban_msgs_regex = [
|
|||||||
|
|
||||||
[normal_chat] #普通聊天
|
[normal_chat] #普通聊天
|
||||||
willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical,mxp模式:mxp,自定义模式:custom(需要你自己实现)
|
willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical,mxp模式:mxp,自定义模式:custom(需要你自己实现)
|
||||||
response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数
|
|
||||||
|
|
||||||
[tool]
|
[tool]
|
||||||
enable_in_normal_chat = false # 是否在普通聊天中启用工具
|
enable_tool = false # 是否在普通聊天中启用工具
|
||||||
enable_in_focus_chat = true # 是否在专注聊天中启用工具
|
|
||||||
|
|
||||||
[emoji]
|
[emoji]
|
||||||
emoji_chance = 0.6 # 麦麦激活表情包动作的概率
|
emoji_chance = 0.6 # 麦麦激活表情包动作的概率
|
||||||
|
|||||||
Reference in New Issue
Block a user