Merge branch 'dev' into dev

This commit is contained in:
UnCLAS-Prommer
2025-07-28 21:59:48 +08:00
committed by GitHub
35 changed files with 1734 additions and 3146 deletions

View File

@@ -5,147 +5,126 @@
## 导入方式
```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]:
```
**参数:**
- `platform`:平台筛选,默认为"qq"
**Args**:
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的聊天流。
**返回:**
**Returns**:
- `List[ChatStream]`:聊天流列表
**示例:**
### 2. 获取群聊聊天流
```python
streams = chat_api.get_all_streams()
for stream in streams:
print(f"聊天流ID: {stream.stream_id}")
def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
```
#### `get_group_streams(platform: str = "qq") -> List[ChatStream]`
获取所有群聊聊天流
**Args**:
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。
**参数:**
- `platform`:平台筛选,默认为"qq"
**返回:**
**Returns**:
- `List[ChatStream]`:群聊聊天流列表
#### `get_private_streams(platform: str = "qq") -> List[ChatStream]`
获取所有私聊聊天流
### 3. 获取私聊聊天流
**参数:**
- `platform`:平台筛选,默认为"qq"
```python
def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
```
**返回:**
**Args**:
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。
**Returns**:
- `List[ChatStream]`:私聊聊天流列表
### 2. 查找特定聊天流
### 4. 根据群ID获取聊天流
#### `get_stream_by_group_id(group_id: str, platform: str = "qq") -> Optional[ChatStream]`
根据群ID获取聊天流
```python
def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
```
**参数:**
**Args**:
- `group_id`群聊ID
- `platform`:平台,默认为"qq"
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。
**返回:**
**Returns**:
- `Optional[ChatStream]`聊天流对象如果未找到返回None
**示例:**
### 5. 根据用户ID获取私聊流
```python
chat_stream = chat_api.get_stream_by_group_id("123456789")
if chat_stream:
print(f"找到群聊: {chat_stream.group_info.group_name}")
def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
```
#### `get_stream_by_user_id(user_id: str, platform: str = "qq") -> Optional[ChatStream]`
根据用户ID获取私聊流
**参数:**
**Args**:
- `user_id`用户ID
- `platform`:平台,默认为"qq"
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。
**返回:**
**Returns**:
- `Optional[ChatStream]`聊天流对象如果未找到返回None
### 3. 聊天流信息查询
### 6. 获取聊天流类型
#### `get_stream_type(chat_stream: ChatStream) -> str`
获取聊天流类型
```python
def get_stream_type(chat_stream: ChatStream) -> str:
```
**参数:**
**Args**:
- `chat_stream`:聊天流对象
**返回:**
- `str`:聊天类型 ("group", "private", "unknown")
**Returns**:
- `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`:聊天流对象
**返回:**
- `Dict[str, Any]`:聊天流信息字典包含stream_id、platform、type等信息
**Returns**:
- `Dict[str, Any]`:聊天流的详细信息,包括但不限于:
- `stream_id`聊天流ID
- `platform`:平台名称
- `type`:聊天流类型
- `group_id`群聊ID
- `group_name`:群聊名称
- `user_id`用户ID
- `user_name`:用户名称
### 8. 获取聊天流统计摘要
**示例:**
```python
info = chat_api.get_stream_info(chat_stream)
print(f"聊天类型: {info['type']}")
print(f"平台: {info['platform']}")
if info['type'] == 'group':
print(f"群ID: {info['group_id']}")
print(f"群名: {info['group_name']}")
def get_streams_summary() -> Dict[str, int]:
```
#### `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. 所有函数都有错误处理,失败时会记录日志
2. 查询函数返回None或空列表时表示未找到结果
3. `platform`参数通常为"qq",也可能支持其他平台
4. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等
1. 大部分函数在参数不合法时候会抛出异常,请确保你的程序进行了捕获。
2. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等。

View 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]` - 禁用的组件名称列表。

View File

@@ -6,178 +6,47 @@
```python
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
# 获取机器人昵称
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")
# 获取嵌套配置
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
# 在插件中使用
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
def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any:
```
**Args**:
- `plugin_config`: 插件配置字典
- `key`: 配置键名,支持嵌套访问如 "section.subsection.key",大小写敏感
- `default`: 如果配置不存在时返回的默认值
### 2. 用户信息API
#### `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")
```
**Returns**:
- `Any`: 配置值或默认值
## 注意事项
1. **只读访问**配置API只提供读取功能插件不能修改全局配置
2. **异步函数**:用户信息相关的函数是异步的,需要使用`await`
3. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值
4. **安全性**插件通过此API访问配置是安全和隔离的
5. **性能**:频繁访问的配置建议在插件初始化时获取并缓存
2. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值
3. **安全性**插件通过此API访问配置是安全和隔离的
4. **性**频繁访问配置建议在插件初始化时获取并缓存

View File

@@ -6,72 +6,51 @@
```python
from src.plugin_system.apis import database_api
# 或者
from src.plugin_system import database_api
```
## 主要功能
### 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. 基本查询操作
### 1. 通用数据库操作
```python
from src.plugin_system.apis import database_api
from src.common.database.database_model import Messages, ActionRecords
async def db_query(
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,
query_type="get",
@@ -79,180 +58,159 @@ messages = await database_api.db_query(
limit=10,
order_by=["-time"]
)
# 查询单条记录
message = await database_api.db_query(
Messages,
query_type="get",
filters={"message_id": "msg_123"},
single_result=True
)
```
### 2. 创建记录
2. 创建一条记录
```python
# 创建新的动作记录
new_record = await database_api.db_query(
ActionRecords,
data={"action_id": "123", "time": time.time(), "action_name": "TestAction"},
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
# 更新动作状态
updated_count = await database_api.db_query(
ActionRecords,
data={"action_done": True},
query_type="update",
filters={"action_id": "action_123"},
data={"action_done": True, "completion_time": time.time()}
filters={"action_id": "123"},
)
print(f"更新了 {updated_count} 条记录")
```
### 4. 删除记录
4. 删除记录
```python
# 删除过期记录
deleted_count = await database_api.db_query(
ActionRecords,
query_type="delete",
filters={"time__lt": time.time() - 86400} # 删除24小时前的记录
filters={"action_id": "123"}
)
print(f"删除了 {deleted_count} 条过期记录")
```
### 5. 统计查询
5. 计数
```python
# 统计消息数量
message_count = await database_api.db_query(
count = await database_api.db_query(
Messages,
query_type="count",
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
# 使用db_save进行创建或更新
record = await database_api.db_save(
ActionRecords,
{
"action_id": "action_123",
"action_id": "123",
"time": time.time(),
"action_name": "TestAction",
"action_done": True
},
key_field="action_id",
key_value="action_123"
key_value="123"
)
```
# 使用db_get进行简单查询
recent_messages = await database_api.db_get(
### 3. 数据库获取
```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,
filters={"chat_id": chat_stream.stream_id},
limit=10,
order_by="-time",
limit=5
)
```
## 高级用法
### 复杂查询示例
### 4. 动作信息存储
```python
# 查询特定用户在特定时间段的消息
user_messages = await database_api.db_query(
Messages,
query_type="get",
filters={
"user_id": "123456",
"time__gte": start_time, # 大于等于开始时间
"time__lt": end_time # 小于结束时间
},
order_by=["-time"],
limit=50
)
# 批量处理
for message in user_messages:
print(f"消息内容: {message['plain_text']}")
print(f"发送时间: {message['time']}")
async def store_action_info(
chat_stream=None,
action_build_into_prompt: bool = False,
action_prompt_display: str = "",
action_done: bool = True,
thinking_id: str = "",
action_data: Optional[dict] = None,
action_name: str = "",
) -> Optional[Dict[str, Any]]:
```
存储动作信息到数据库,是一种针对 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
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
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"
)
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的事务功能

View File

@@ -6,11 +6,13 @@
```python
from src.plugin_system.apis import emoji_api
# 或者
from src.plugin_system import emoji_api
```
## 🆕 **二步走识别优化**
## 二步走识别优化
新版本开始,表情包识别系统采用了**二步走识别 + 智能缓存**的优化方案:
从新版本开始,表情包识别系统采用了**二步走识别 + 智能缓存**的优化方案:
### **收到表情包时的识别流程**
1. **第一步**VLM视觉分析 - 生成详细描述
@@ -30,217 +32,84 @@ from src.plugin_system.apis import emoji_api
## 主要功能
### 1. 表情包获取
#### `get_by_description(description: str) -> Optional[Tuple[str, str, str]]`
```python
async def get_by_description(description: str) -> Optional[Tuple[str, str, str]]:
```
根据场景描述选择表情包
**参数**
- `description`场景描述文本,例如"开心的大笑"、"轻微的讽刺"、"表示无奈和沮丧"等
**Args**
- `description`表情包的描述文本,例如"开心"、"难过"、"愤怒"等
**返回**
- `Optional[Tuple[str, str, str]]`(base64编码, 表情包描述, 匹配的场景) 或 None
**Returns**
- `Optional[Tuple[str, str, str]]`一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到匹配的表情包则返回None
**示例:**
#### 示例
```python
emoji_result = await emoji_api.get_by_description("开心的大笑")
emoji_result = await emoji_api.get_by_description("大笑")
if emoji_result:
emoji_base64, description, matched_scene = emoji_result
print(f"获取到表情包: {description}, 场景: {matched_scene}")
# 可以将emoji_base64用于发送表情包
```
#### `get_random() -> Optional[Tuple[str, str, str]]`
随机获取表情包
**返回:**
- `Optional[Tuple[str, str, str]]`(base64编码, 表情包描述, 随机场景) 或 None
**示例:**
### 2. 随机获取表情包
```python
random_emoji = await emoji_api.get_random()
if random_emoji:
emoji_base64, description, scene = random_emoji
print(f"随机表情包: {description}")
async def get_random(count: Optional[int] = 1) -> List[Tuple[str, str, str]]:
```
随机获取指定数量的表情包
#### `get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]`
根据场景关键词获取表情包
**Args**
- `count`:要获取表情包数量默认为1
**参数**
- `emotion`:场景关键词,如"大笑"、"讽刺"、"无奈"等
**Returns**
- `List[Tuple[str, str, str]]`:一个包含多个表情包的列表,每个元素是一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到或出错则返回空列表
**返回:**
- `Optional[Tuple[str, str, str]]`(base64编码, 表情包描述, 匹配的场景) 或 None
**示例:**
### 3. 根据情感获取表情包
```python
emoji_result = await emoji_api.get_by_emotion("讽刺")
if emoji_result:
emoji_base64, description, scene = emoji_result
# 发送讽刺表情包
async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]:
```
根据情感标签获取表情包
### 2. 表情包信息查询
**Args**
- `emotion`:情感标签,例如"开心"、"悲伤"、"愤怒"等
#### `get_count() -> int`
获取表情包数量
**Returns**
- `Optional[Tuple[str, str, str]]`:一个元组: (表情包的base64编码, 描述, 情感标签)如果未找到则返回None
**返回:**
- `int`:当前可用的表情包数量
### 4. 获取表情包数量
```python
def get_count() -> int:
```
获取当前可用表情包的数量
#### `get_info() -> dict`
获取表情包系统信息
### 5. 获取表情包系统信息
```python
def get_info() -> Dict[str, Any]:
```
获取表情包系统的基本信息
**返回**
- `dict`:包含表情包数量、最大数量等信息
**返回字典包含:**
**Returns**
- `Dict[str, Any]`:包含表情包数量、描述等信息的字典,包含以下键:
- `current_count`:当前表情包数量
- `max_count`:最大表情包数量
- `available_emojis`:可用表情包数量
- `available_emojis`当前可用表情包数量
#### `get_emotions() -> list`
获取所有可用的场景关键词
### 6. 获取所有可用的情感标签
```python
def get_emotions() -> List[str]:
```
获取所有可用的情感标签 **(已经去重)**
**返回:**
- `list`:所有表情包的场景关键词列表(去重)
#### `get_descriptions() -> list`
### 7. 获取所有表情包描述
```python
def get_descriptions() -> List[str]:
```
获取所有表情包的描述列表
**返回:**
- `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编码返回可直接用于发送
3. **错误处理**所有函数都有错误处理失败时返回None或默认值
3. **错误处理**所有函数都有错误处理失败时返回None,空列表或默认值
4. **使用统计**:系统会记录表情包的使用次数
5. **文件依赖**:表情包依赖于本地文件,确保表情包文件存在
6. **编码格式**返回的是base64编码的图片数据可直接用于网络传输

View File

@@ -6,241 +6,150 @@
```python
from src.plugin_system.apis import generator_api
# 或者
from src.plugin_system import generator_api
```
## 主要功能
### 1. 回复器获取
#### `get_replyer(chat_stream=None, platform=None, chat_id=None, is_group=True)`
```python
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`:聊天流对象(优先)
- `platform`:平台名称,如"qq"
- `chat_id`聊天ID群ID或用户ID
- `is_group`:是否为群聊
优先使用chat_stream如果没有则使用chat_id直接查找。
**返回:**
- `DefaultReplyer`回复器对象如果获取失败则返回None
使用 ReplyerManager 来管理实例,避免重复创建。
**示例:**
**Args:**
- `chat_stream`: 聊天流对象
- `chat_id`: 聊天ID实际上就是`stream_id`
- `model_configs`: 模型配置
- `request_type`: 请求类型用于记录LLM使用情况可以不写
**Returns:**
- `DefaultReplyer`: 回复器对象如果获取失败则返回None
#### 示例
```python
# 使用聊天流获取回复器
replyer = generator_api.get_replyer(chat_stream=chat_stream)
# 使用平台和ID获取回复器
replyer = generator_api.get_replyer(
platform="qq",
chat_id="123456789",
is_group=True
)
replyer = generator_api.get_replyer(chat_id="123456789")
```
### 2. 回复生成
#### `generate_reply(chat_stream=None, action_data=None, platform=None, chat_id=None, is_group=True)`
```python
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`:聊天流对象(优先)
- `action_data`:动作数据
- `platform`:平台名称(备用)
- `chat_id`聊天ID备用
- `is_group`:是否为群聊(备用)
优先使用chat_stream如果没有则使用chat_id直接查找。
**返回:**
- `Tuple[bool, List[Tuple[str, Any]]]`(是否成功, 回复集合)
**Args:**
- `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
success, reply_set = await generator_api.generate_reply(
success, reply_set, prompt = await generator_api.generate_reply(
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:
for reply_type, reply_content in reply_set:
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)`
重写回复
**参数:**
- `chat_stream`:聊天流对象(优先)
- `reply_data`:回复数据
- `platform`:平台名称(备用)
- `chat_id`聊天ID备用
- `is_group`:是否为群聊(备用)
**返回:**
- `Tuple[bool, List[Tuple[str, Any]]]`(是否成功, 回复集合)
**示例:**
### 3. 回复重写
```python
success, reply_set = await generator_api.rewrite_reply(
chat_stream=chat_stream,
reply_data={"original_text": "原始回复", "style": "more_friendly"}
)
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直接查找。
### 1. 基础回复生成
**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
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(
success, reply_set, prompt = await generator_api.rewrite_reply(
chat_stream=chat_stream,
action_data=action_data
raw_reply="原始回复内容",
reason="重写原因",
reply_to="麦麦:你好",
return_prompt=True
)
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": "抱歉,我现在无法理解您的消息。"
}
for reply_type, reply_content in reply_set:
print(f"回复类型: {reply_type}, 内容: {reply_content}")
if prompt:
print(f"使用的提示词: {prompt}")
```
### 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 []
```
## 回复集合格式
## 回复集合`reply_set`格式
### 回复类型
生成的回复集合包含多种类型的回复:
@@ -260,82 +169,32 @@ reply_set = [
]
```
## 高级用法
### 1. 自定义回复器配置
### 4. 自定义提示词回复
```python
async def generate_with_custom_config(chat_stream, action_data):
"""使用自定义配置生成回复"""
# 获取回复器
replyer = generator_api.get_replyer(chat_stream=chat_stream)
if replyer:
# 可以访问回复器的内部方法
success, reply_set = await replyer.generate_reply_with_context(
reply_data=action_data,
# 可以传递额外的配置参数
)
return success, reply_set
return False, []
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]:
```
生成自定义提示词回复
### 2. 回复质量评估
优先使用chat_stream如果没有则使用chat_id直接查找。
```python
async def generate_and_evaluate_replies(chat_stream, action_data):
"""生成回复并评估质量"""
**Args:**
- `chat_stream`: 聊天流对象
- `chat_id`: 聊天ID备用
- `model_configs`: 模型配置列表
- `prompt`: 自定义提示词
success, reply_set = await generator_api.generate_reply(
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):
"""简单的回复质量评估"""
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)
```
**Returns:**
- `Optional[str]`: 生成的自定义回复内容如果生成失败则返回None
## 注意事项
1. **异步操作**所有生成函数是异步的,须使用`await`
2. **错误处理**函数内置错误处理失败时返回False和空列表
3. **聊天流依赖**:需要有效的聊天流对象才能正常工作
4. **性能考虑**回复生成可能需要一些时间特别是使用LLM时
5. **回复格式**:返回的回复集合是元组列表,包含类型和内容
6. **上下文感知**:生成器会考虑聊天上下文和历史消息
1. **异步操作**部分函数是异步的,须使用`await`
2. **聊天流依赖**:需要有效的聊天流对象才能正常工作
3. **性能考虑**回复生成可能需要一些时间特别是使用LLM时
4. **回复格式**:返回的回复集合是元组列表,包含类型和内容
5. **上下文感知**:生成器会考虑聊天上下文和历史消息,除非你用的是自定义提示词。

View File

@@ -6,239 +6,34 @@ LLM API模块提供与大语言模型交互的功能让插件能够使用系
```python
from src.plugin_system.apis import llm_api
# 或者
from src.plugin_system import llm_api
```
## 主要功能
### 1. 模型管理
#### `get_available_models() -> Dict[str, Any]`
获取所有可用的模型配置
**返回:**
- `Dict[str, Any]`模型配置字典key为模型名称value为模型配置
**示例:**
### 1. 查询可用模型
```python
models = llm_api.get_available_models()
for model_name, model_config in models.items():
print(f"模型: {model_name}")
print(f"配置: {model_config}")
def get_available_models() -> Dict[str, Any]:
```
获取所有可用的模型配置。
### 2. 内容生成
**Return**
- `Dict[str, Any]`模型配置字典key为模型名称value为模型配置。
#### `generate_with_model(prompt, model_config, request_type="plugin.generate", **kwargs)`
使用指定模型生成内容
**参数:**
- `prompt`:提示词
- `model_config`:模型配置(从 get_available_models 获取)
- `request_type`:请求类型标识
- `**kwargs`其他模型特定参数如temperature、max_tokens等
**返回:**
- `Tuple[bool, str, str, str]`(是否成功, 生成的内容, 推理过程, 模型名称)
**示例:**
### 2. 使用模型生成内容
```python
models = llm_api.get_available_models()
default_model = models.get("default")
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}")
async def generate_with_model(
prompt: str, model_config: Dict[str, Any], request_type: str = "plugin.generate", **kwargs
) -> Tuple[bool, str]:
```
使用指定模型生成内容。
## 使用示例
**Args:**
- `prompt`:提示词。
- `model_config`:模型配置(从 `get_available_models` 获取)。
- `request_type`:请求类型标识,默认为 `"plugin.generate"`
- `**kwargs`:其他模型特定参数,如 `temperature``max_tokens` 等。
### 1. 基础文本生成
```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以控制成本
**Return**
- `Tuple[bool, str]`:返回一个元组,第一个元素表示是否成功,第二个元素为生成的内容或错误信息。

View 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`

View File

@@ -1,11 +1,13 @@
# 消息API
> 消息API提供了强大的消息查询、计数和格式化功能让你轻松处理聊天消息数据。
消息API提供了强大的消息查询、计数和格式化功能让你轻松处理聊天消息数据。
## 导入方式
```python
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]]:
```
获取指定时间范围内的消息。
### 按时间查询消息
#### `get_messages_by_time(start_time, end_time, limit=0, limit_mode="latest")`
获取指定时间范围内的消息
**参数:**
**Args:**
- `start_time` (float): 开始时间戳
- `end_time` (float): 结束时间戳
- `limit` (int): 限制返回消息数量0为不限制
- `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
import time
# 获取最近24小时的消息
now = time.time()
yesterday = now - 24 * 3600
messages = message_api.get_messages_by_time(yesterday, now, limit=50)
def get_messages_by_time_in_chat(
chat_id: str,
start_time: float,
end_time: float,
limit: int = 0,
limit_mode: str = "latest",
filter_mai: bool = False,
) -> List[Dict[str, Any]]:
```
获取指定聊天中指定时间范围内的消息。
### 按聊天查询消息
#### `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")`
获取指定聊天中指定用户在指定时间范围内的消息
**参数:**
**Args:**
- `chat_id` (str): 聊天ID
- `start_time` (float): 开始时间戳
- `end_time` (float): 结束时间戳
- `person_ids` (list): 用户ID列表
- `limit` (int): 限制返回消息数量
- `limit_mode` (str): 限制模式
- `limit` (int): 限制返回消息数量0为不限制
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
- `filter_mai` (bool): 是否过滤掉机器人的消息默认False
**示例:**
**Returns:**
- `List[Dict[str, Any]]` - 消息列表
### 3. 获取指定聊天中指定时间范围内的信息(包含边界)
```python
# 获取特定用户的消息
user_messages = message_api.get_messages_by_time_in_chat_for_users(
chat_id="123456789",
start_time=yesterday,
end_time=now,
person_ids=["user1", "user2"]
)
def get_messages_by_time_in_chat_inclusive(
chat_id: str,
start_time: float,
end_time: float,
limit: int = 0,
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]]:
```
获取指定聊天中指定用户在指定时间范围内的消息。
随机选择一个聊天,返回该聊天在指定时间范围内的消息
#### `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)`
计算指定聊天中从开始时间到结束时间的新消息数量
**参数:**
**Args:**
- `chat_id` (str): 聊天ID
- `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
# 计算最近1小时的新消息数
import time
now = time.time()
hour_ago = now - 3600
new_count = message_api.count_new_messages("123456789", hour_ago, now)
print(f"最近1小时有{new_count}条新消息")
def get_random_chat_messages(
start_time: float,
end_time: float,
limit: int = 0,
limit_mode: str = "latest",
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]]): 消息列表
- `replace_bot_name` (bool): 是否将机器人的名称替换为"你"默认True
- `merge_messages` (bool): 是否合并连续消息默认False
- `timestamp_mode` (str): 时间戳显示模式,`"relative"``"absolute"`,默认`"relative"`
- `read_mark` (float): 已读标记时间戳,用于分割已读和未读消息默认0.0
- `truncate` (bool): 是否截断长消息默认False
- `show_actions` (bool): 是否显示动作记录默认False
- `replace_bot_name` (bool): 是否将机器人的名称替换为"你"
- `merge_messages` (bool): 是否合并连续消息
- `timestamp_mode` (str): 时间戳显示模式,`"relative"``"absolute"`
- `read_mark` (float): 已读标记时间戳,用于分割已读和未读消息
- `truncate` (bool): 是否截断长消息
- `show_actions` (bool): 是否显示动作记录
**返回:** `str` - 格式化后的可读字符串
**Returns:**
- `str` - 格式化后的可读字符串
**示例:**
### 14. 将消息列表构建成可读的字符串,并返回详细信息
```python
# 获取消息并格式化为可读文本
messages = message_api.get_recent_messages("123456789", hours=2)
readable_text = message_api.build_readable_messages_to_str(
messages,
replace_bot_name=True,
merge_messages=True,
timestamp_mode="relative"
)
print(readable_text)
async def build_readable_messages_with_details(
messages: List[Dict[str, Any]],
replace_bot_name: bool = True,
merge_messages: bool = False,
timestamp_mode: str = "relative",
truncate: bool = False,
) -> Tuple[str, List[Tuple[float, str, str]]]:
```
将消息列表构建成可读的字符串,并返回详细信息。
### `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
# 异步获取详细格式化信息
readable_text, details = await message_api.build_readable_messages_with_details(
messages,
timestamp_mode="absolute"
)
for timestamp, nickname, content in details:
print(f"{timestamp}: {nickname} 说: {content}")
async def get_person_ids_from_messages(
messages: List[Dict[str, Any]],
) -> List[str]:
```
从消息列表中提取不重复的用户ID列表。
### `get_person_ids_from_messages(messages)` 异步
从消息列表中提取不重复的用户ID列表
**参数:**
**Args:**
- `messages` (List[Dict[str, Any]]): 消息列表
**返回:** `List[str]` - 用户ID列表
**Returns:**
- `List[str]` - 用户ID列表
**示例:**
### 16. 从消息列表中移除机器人的消息
```python
# 获取参与对话的所有用户ID
messages = message_api.get_recent_messages("123456789")
person_ids = await message_api.get_person_ids_from_messages(messages)
print(f"参与对话的用户: {person_ids}")
def filter_mai_messages(
messages: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
```
从消息列表中移除机器人的消息。
---
**Args:**
- `messages` (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
```
---
**Returns:**
- `List[Dict[str, Any]]` - 过滤后的消息列表
## 注意事项
1. **时间戳格式**所有时间参数都使用Unix时间戳float类型
2. **异步函数**`build_readable_messages_with_details``get_person_ids_from_messages` 是异步函数,需要使用 `await`
2. **异步函数**部分函数是异步函数,需要使用 `await`
3. **性能考虑**:查询大量消息时建议设置合理的 `limit` 参数
4. **消息格式**:返回的消息是字典格式,包含时间戳、发送者、内容等信息
5. **用户ID**`person_ids` 参数接受字符串列表,用于筛选特定用户的消息

View File

@@ -6,59 +6,65 @@
```python
from src.plugin_system.apis import person_api
# 或者
from src.plugin_system import person_api
```
## 主要功能
### 1. Person ID管理
#### `get_person_id(platform: str, user_id: int) -> str`
### 1. Person ID 获取
```python
def get_person_id(platform: str, user_id: int) -> str:
```
根据平台和用户ID获取person_id
**参数:**
**Args:**
- `platform`:平台名称,如 "qq", "telegram" 等
- `user_id`用户ID
**返回:**
**Returns:**
- `str`唯一的person_idMD5哈希值
**示例:**
#### 示例
```python
person_id = person_api.get_person_id("qq", 123456)
print(f"Person ID: {person_id}")
```
### 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`
根据person_id和字段名获取某个值
**参数:**
**Args:**
- `person_id`用户的唯一标识ID
- `field_name`:要获取的字段名,如 "nickname", "impression" 等
- `default`字段不存在或获取失败时返回的默认值
- `field_name`:要获取的字段名
- `default`:字段不存在的默认值
**返回:**
**Returns:**
- `Any`:字段值或默认值
**示例:**
#### 示例
```python
nickname = await person_api.get_person_value(person_id, "nickname", "未知用户")
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
- `field_names`:要获取的字段名列表
- `default_dict`:默认值字典,键为字段名,值为默认值
**返回:**
**Returns:**
- `dict`:字段名到值的映射字典
**示例:**
#### 示例
```python
values = await person_api.get_person_values(
person_id,
@@ -67,204 +73,31 @@ values = await person_api.get_person_values(
)
```
### 3. 用户状态查询
#### `is_person_known(platform: str, user_id: int) -> bool`
### 4. 判断用户是否已知
```python
async def is_person_known(platform: str, user_id: int) -> bool:
```
判断是否认识某个用户
**参数:**
**Args:**
- `platform`:平台名称
- `user_id`用户ID
**返回:**
**Returns:**
- `bool`:是否认识该用户
**示例:**
### 5. 根据用户名获取Person ID
```python
known = await person_api.is_person_known("qq", 123456)
if known:
print("这个用户我认识")
def get_person_id_by_name(person_name: str) -> str:
```
### 4. 用户名查询
#### `get_person_id_by_name(person_name: str) -> str`
根据用户名获取person_id
**参数:**
**Args:**
- `person_name`:用户名
**返回:**
**Returns:**
- `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`:对用户的印象
- `know_times`:交互次数
- `relationship_level`:关系等级
- `last_seen`:最后见面时间
- `last_interaction`:最后交互时间
- `points`: 用户特征点
### 个性化字段
- `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
```
其他字段可以参考`PersonInfo`类的属性(位于`src.common.database.database_model`
## 注意事项
1. **异步操作**部分查询函数都是异步的,需要使用`await`
2. **错误处理**:所有函数都有错误处理,失败时记录日志并返回默认值
3. **数据类型**返回的数据可能是字符串、数字或JSON需要适当处理
4. **性能考虑**:批量查询优于单个查询
5. **隐私保护**:确保用户信息的使用符合隐私政策
6. **数据一致性**person_id是用户的唯一标识应妥善保存和使用
1. **异步操作**:部分查询函数都是异步的,需要使用`await`
2. **性能考虑**:批量查询优于单个查询
3. **隐私保护**:确保用户信息的使用符合隐私政策
4. **数据一致性**person_id是用户的唯一标识应妥善保存和使用

View 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]` - 成功加载的插件数量和失败的插件数量。

View File

@@ -6,86 +6,108 @@
```python
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): 是否存储消息到数据库
**参数:**
- `text`:要发送的文本内容
- `group_id`群聊ID
- `platform`:平台,默认为"qq"
- `typing`:是否显示正在输入
- `reply_to`:回复消息的格式,如"发送者:消息内容"
- `storage_message`:是否存储到数据库
**Returns:**
- `bool` - 是否发送成功
**返回:**
- `bool`:是否发送成功
### 2. 发送表情包
```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): 是否存储消息到数据库
**参数:**
- `emoji_base64`表情包的base64编码
- `group_id`群聊ID
- `platform`:平台,默认为"qq"
- `storage_message`:是否存储到数据库
**Returns:**
- `bool` - 是否发送成功
#### `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)`
向群聊发送命令
#### `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`:是否存储
**Returns:**
- `bool` - 是否发送成功
## 使用示例
### 1. 基础文本发送
### 1. 基础文本发送,并回复消息
```python
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):
"""发送问候消息"""
if chat_stream.group_info:
# 群聊
success = await send_api.text_to_group(
text="大家好!",
group_id=chat_stream.group_info.group_id,
typing=True
)
else:
# 私聊
success = await send_api.text_to_user(
text="你好!",
user_id=chat_stream.user_info.user_id,
typing=True
success = await send_api.text_to_stream(
text="Hello, world!",
stream_id=chat_stream.stream_id,
typing=True,
reply_to="User:How are you?",
storage_message=True
)
return success
```
### 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. 发送表情包
### 2. 发送表情包
```python
from src.plugin_system.apis import emoji_api
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)
if not emoji_result:
@@ -152,107 +140,10 @@ async def send_emoji_reaction(chat_stream, emotion):
emoji_base64, description, matched_emotion = emoji_result
# 发送表情包
if chat_stream.group_info:
success = await send_api.emoji_to_group(
success = await send_api.emoji_to_stream(
emoji_base64=emoji_base64,
group_id=chat_stream.group_info.group_id
)
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", "")
stream_id=chat_stream.stream_id,
storage_message=False # 不存储到数据库
)
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`

View File

@@ -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文件使用可读性好的缩进格式

View File

@@ -43,19 +43,22 @@ Command vs Action 选择指南
- [LLM API](api/llm-api.md) - 大语言模型交互接口可以使用内置LLM生成内容
- [✨ 回复生成器API](api/generator-api.md) - 智能回复生成接口,可以使用内置风格化生成器
### 表情包api
### 表情包API
- [😊 表情包API](api/emoji-api.md) - 表情包选择和管理接口
### 关系系统api
### 关系系统API
- [人物信息API](api/person-api.md) - 用户信息,处理麦麦认识的人和关系的接口
### 数据与配置API
- [🗄️ 数据库API](api/database-api.md) - 数据库操作接口
- [⚙️ 配置API](api/config-api.md) - 配置读取和用户信息接口
### 工具API
- [工具API](api/utils-api.md) - 文件操作、时间处理等工具函数
### 插件和组件管理API
- [🔌 插件API](api/plugin-manage-api.md) - 插件加载和管理接口
- [🧩 组件API](api/component-manage-api.md) - 组件注册和管理接口
### 日志API
- [📜 日志API](api/logging-api.md) - logger实例获取接口
## 实验性

View File

@@ -2,7 +2,7 @@ import asyncio
import time
import traceback
import random
from typing import List, Optional, Dict, Any
from typing import List, Optional, Dict, Any, Tuple
from rich.traceback import install
from src.config.config import global_config
@@ -219,7 +219,9 @@ class HeartFChatting:
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
self.energy_value = (
10 + (len(new_messages_data) / (3 / pow(global_config.chat.focus_value, 0.5))) * 10
)
return True
if self.energy_value >= 30:
@@ -261,9 +263,10 @@ class HeartFChatting:
reply_to_str,
loop_start_time,
action_message,
cycle_timers,
cycle_timers: Dict[str, float],
thinking_id,
plan_result):
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)
@@ -286,9 +289,8 @@ class HeartFChatting:
action_name="reply",
)
# 构建循环信息
loop_info = {
loop_info: Dict[str, Any] = {
"loop_plan_info": {
"action_result": plan_result.get("action_result", {}),
},
@@ -337,8 +339,9 @@ class HeartFChatting:
skip_planner = False
if self.loop_mode == ChatMode.NORMAL:
# 过滤掉reply相关的动作检查是否还有其他动作
non_reply_actions = {k: v for k, v in available_actions.items()
if k not in ['reply', 'no_reply', 'no_action']}
non_reply_actions = {
k: v for k, v in available_actions.items() if k not in ["reply", "no_reply", "no_action"]
}
if not non_reply_actions:
skip_planner = True
@@ -366,17 +369,20 @@ class HeartFChatting:
# 如果normal模式且不跳过规划器开始一个回复生成进程先准备好回复其实是和planer同时进行的
if not skip_planner:
reply_to_str = await self.build_reply_to_str(message_data)
gen_task = asyncio.create_task(self._generate_response(
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"))
request_type="chat.replyer.normal",
)
)
if not skip_planner:
with Timer("规划器", cycle_timers):
plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode)
action_result: dict = plan_result.get("action_result", {}) # type: ignore
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", {}),
@@ -386,29 +392,27 @@ class HeartFChatting:
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}动作"
)
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作")
else:
# 只有在gen_task存在时才进行相关操作
if gen_task is not None:
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},不发表回复"
)
else:
content = " ".join([item[1] for item in gen_task.result() if item[0] == "text"])
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":
@@ -417,11 +421,14 @@ class HeartFChatting:
# 只有在gen_task存在时才等待
if not gen_task:
reply_to_str = await self.build_reply_to_str(message_data)
gen_task = asyncio.create_task(self._generate_response(
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"))
request_type="chat.replyer.normal",
)
)
gather_timeout = global_config.chat.thinking_timeout
try:
@@ -446,30 +453,37 @@ class HeartFChatting:
message_data=action_message,
available_actions=available_actions,
reply_to=reply_to_str,
request_type="chat.replyer.focus")
request_type="chat.replyer.focus",
)
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)
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
else:
# 并行执行:同时进行回复发送和动作执行
tasks = []
# 先置空防止未定义错误
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():
async def handle_reply_task() -> Tuple[Optional[Dict[str, Any]], str, Dict[str, float]]:
# 等待预生成的回复任务完成
gather_timeout = global_config.chat.thinking_timeout
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已跳过")
logger.warning(
f"{self.log_prefix} 并行执行:回复生成超时>{global_config.chat.thinking_timeout}s已跳过"
)
return None, "", {}
except asyncio.CancelledError:
logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消")
@@ -480,11 +494,19 @@ class HeartFChatting:
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)
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
# 添加回复任务到并行任务列表
tasks.append(asyncio.create_task(handle_reply_task()))
# 执行回复任务并赋值到变量
background_reply_task = asyncio.create_task(handle_reply_task())
# 动作执行任务
async def handle_action_task():
@@ -494,51 +516,54 @@ class HeartFChatting:
)
return success, reply_text, command
# 添加动作执行任务到并行任务列表
tasks.append(asyncio.create_task(handle_action_task()))
# 执行动作任务并赋值到变量
background_action_task = asyncio.create_task(handle_action_task())
# 并行执行所有任务
results = await asyncio.gather(*tasks, return_exceptions=True)
# 处理结果
reply_loop_info = None
reply_text_from_reply = ""
action_success = False
action_reply_text = ""
action_command = ""
if len(tasks) == 2: # 有回复任务和动作任务
# 并行执行所有任务
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, Exception):
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
# 处理动作任务结果
action_result = results[1]
if isinstance(action_result, Exception):
logger.error(f"{self.log_prefix} 动作任务执行异常: {action_result}")
action_task_result = results[1]
if isinstance(action_task_result, BaseException):
logger.error(f"{self.log_prefix} 动作任务执行异常: {action_task_result}")
else:
action_success, action_reply_text, action_command = action_result
else: # 只有动作任务
action_result = results[0]
if isinstance(action_result, Exception):
logger.error(f"{self.log_prefix} 动作任务执行异常: {action_result}")
action_success, action_reply_text, action_command = action_task_result
else:
action_success, action_reply_text, action_command = action_result
results = await asyncio.gather(background_action_task, return_exceptions=True)
# 只有动作任务
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 reply_loop_info:
# 如果有回复信息使用回复的loop_info作为基础
loop_info = reply_loop_info
# 更新动作执行信息
loop_info["loop_action_info"].update({
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
@@ -555,7 +580,6 @@ class HeartFChatting:
}
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)
@@ -603,7 +627,7 @@ class HeartFChatting:
action: str,
reasoning: str,
action_data: dict,
cycle_timers: dict,
cycle_timers: Dict[str, float],
thinking_id: str,
action_message: dict,
) -> tuple[bool, str, str]:
@@ -712,7 +736,11 @@ class HeartFChatting:
return False
async def _generate_response(
self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str, request_type: str = "chat.replyer.normal"
self,
message_data: dict,
available_actions: Optional[Dict[str, ActionInfo]],
reply_to: str,
request_type: str = "chat.replyer.normal",
) -> Optional[list]:
"""生成普通回复"""
try:
@@ -734,7 +762,7 @@ class HeartFChatting:
logger.error(f"{self.log_prefix}回复生成出现错误:{str(e)} {traceback.format_exc()}")
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()
new_message_count = message_api.count_new_messages(
chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time
@@ -746,13 +774,9 @@ class HeartFChatting:
need_reply = new_message_count >= random.randint(2, 4)
if need_reply:
logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
)
logger.info(f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复")
else:
logger.info(
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
)
logger.info(f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复")
reply_text = ""
first_replied = False

View File

@@ -444,7 +444,7 @@ class MessageSending(MessageProcessBase):
is_emoji: bool = False,
thinking_start_time: float = 0,
apply_set_reply_logic: bool = False,
reply_to: str = None, # type: ignore
reply_to: Optional[str] = None,
):
# 调用父类初始化
super().__init__(

View File

@@ -211,8 +211,7 @@ class ActionPlanner:
reasoning = f"Planner 内部处理错误: {outer_e}"
is_parallel = False
if mode == ChatMode.NORMAL:
if action in current_available_actions:
if mode == ChatMode.NORMAL and action in current_available_actions:
is_parallel = current_available_actions[action].parallel_action
action_result = {
@@ -276,7 +275,6 @@ class ActionPlanner:
if global_config.chat.at_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你或者at你"
by_what = "聊天内容"
target_prompt = '\n "target_message_id":"触发action的消息id"'
no_action_block = f"""重要说明:

View File

@@ -156,10 +156,18 @@ class DefaultReplyer:
extra_info: str = "",
available_actions: Optional[Dict[str, ActionInfo]] = None,
enable_tool: bool = True,
enable_timeout: bool = False,
) -> Tuple[bool, Optional[str], Optional[str]]:
"""
回复器 (Replier): 核心逻辑,负责生成回复文本。
回复器 (Replier): 负责生成回复文本的核心逻辑
Args:
reply_to: 回复对象,格式为 "发送者:消息内容"
extra_info: 额外信息,用于补充上下文
available_actions: 可用的动作信息字典
enable_tool: 是否启用工具调用
Returns:
Tuple[bool, Optional[str], Optional[str]]: (是否成功, 生成的回复内容, 使用的prompt)
"""
prompt = None
if available_actions is None:
@@ -171,7 +179,6 @@ class DefaultReplyer:
reply_to=reply_to,
extra_info=extra_info,
available_actions=available_actions,
enable_timeout=enable_timeout,
enable_tool=enable_tool,
)
@@ -181,29 +188,12 @@ class DefaultReplyer:
# 4. 调用 LLM 生成回复
content = None
reasoning_content = None
model_name = "unknown_model"
# TODO: 复活这里
# reasoning_content = None
# model_name = "unknown_model"
try:
with Timer("LLM生成", {}): # 内部计时器,可选保留
# 加权随机选择一个模型配置
selected_model_config = self._select_weighted_model_config()
logger.info(
f"使用模型生成回复: {selected_model_config.get('name', 'N/A')} (选中概率: {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)
content = await self.llm_generate_content(prompt)
logger.debug(f"replyer生成内容: {content}")
except Exception as llm_e:
@@ -220,62 +210,54 @@ class DefaultReplyer:
async def rewrite_reply_with_context(
self,
reply_data: Dict[str, Any],
raw_reply: str = "",
reason: str = "",
reply_to: str = "",
relation_info: str = "",
) -> Tuple[bool, Optional[str]]:
return_prompt: bool = False,
) -> Tuple[bool, Optional[str], Optional[str]]:
"""
表达器 (Expressor): 核心逻辑,负责生成回复文本。
表达器 (Expressor): 负责重写和优化回复文本。
Args:
raw_reply: 原始回复内容
reason: 回复原因
reply_to: 回复对象,格式为 "发送者:消息内容"
relation_info: 关系信息
Returns:
Tuple[bool, Optional[str]]: (是否成功, 重写后的回复内容)
"""
try:
if not reply_data:
reply_data = {
"reply_to": reply_to,
"relation_info": relation_info,
}
with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await self.build_prompt_rewrite_context(
reply_data=reply_data,
raw_reply=raw_reply,
reason=reason,
reply_to=reply_to,
)
content = None
reasoning_content = None
model_name = "unknown_model"
# TODO: 复活这里
# reasoning_content = None
# model_name = "unknown_model"
if not prompt:
logger.error("Prompt 构建失败,无法生成回复。")
return False, None
return False, None, None
try:
with Timer("LLM生成", {}): # 内部计时器,可选保留
# 加权随机选择一个模型配置
selected_model_config = self._select_weighted_model_config()
logger.info(
f"使用模型重写回复: {selected_model_config.get('name', 'N/A')} (选中概率: {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)
content = await self.llm_generate_content(prompt)
logger.info(f"想要表达:{raw_reply}||理由:{reason}||生成回复: {content}\n")
except Exception as 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:
logger.error(f"回复生成意外失败: {e}")
traceback.print_exc()
return False, None
return False, None, prompt if return_prompt else None
async def build_relation_info(self, reply_to: str = ""):
if not global_config.relationship.enable_relationship:
@@ -297,7 +279,16 @@ class DefaultReplyer:
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:
return ""
@@ -343,11 +334,18 @@ class DefaultReplyer:
if style_habits_str.strip() and grammar_habits_str.strip():
expression_habits_title = "你可以参考以下的语言习惯和句法,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式结合到你的回复中:"
expression_habits_block = f"{expression_habits_title}\n{expression_habits_block}"
return f"{expression_habits_title}\n{expression_habits_block}"
return expression_habits_block
async def build_memory_block(self, chat_history: str, target: str) -> str:
"""构建记忆块
async def build_memory_block(self, chat_history, target):
Args:
chat_history: 聊天历史记录
target: 目标消息内容
Returns:
str: 记忆信息字符串
"""
if not global_config.memory.enable_memory:
return ""
@@ -375,12 +373,13 @@ class DefaultReplyer:
return memory_str
async def build_tool_info(self, chat_history, reply_to: str = "", enable_tool: bool = True):
async def build_tool_info(self, chat_history: str, reply_to: str = "", enable_tool: bool = True) -> str:
"""构建工具信息块
Args:
reply_data: 回复数据,包含要回复的消息内容
chat_history: 聊天历史
chat_history: 聊天历史记录
reply_to: 回复对象,格式为 "发送者:消息内容"
enable_tool: 是否启用工具调用
Returns:
str: 工具信息字符串
@@ -424,7 +423,15 @@ class DefaultReplyer:
logger.error(f"工具信息获取失败: {e}")
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 = ""
target = ""
# 添加None检查防止NoneType错误
@@ -438,7 +445,15 @@ class DefaultReplyer:
target = parts[1].strip()
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 = ""
try:
@@ -472,15 +487,25 @@ class DefaultReplyer:
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()
result = await coroutine
end_time = time.time()
duration = end_time - start_time
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
@@ -489,7 +514,7 @@ class DefaultReplyer:
target_user_id: 目标用户ID当前对话对象
Returns:
tuple: (核心对话prompt, 背景对话prompt)
Tuple[str, str]: (核心对话prompt, 背景对话prompt)
"""
core_dialogue_list = []
background_dialogue_list = []
@@ -508,7 +533,7 @@ class DefaultReplyer:
# 其他用户的对话
background_dialogue_list.append(msg_dict)
except Exception as e:
logger.error(f"![1753364551656](image/default_generator/1753364551656.png)记录: {msg_dict}, 错误: {e}")
logger.error(f"处理消息记录时出错: {msg_dict}, 错误: {e}")
# 构建背景对话 prompt
background_dialogue_prompt = ""
@@ -553,8 +578,25 @@ class DefaultReplyer:
sender: str,
target: str,
chat_info: str,
):
"""构建 mai_think 上下文信息"""
) -> Any:
"""构建 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.memory_block = memory_block
mai_think.relation_info_block = relation_info
@@ -573,19 +615,17 @@ class DefaultReplyer:
reply_to: str,
extra_info: str = "",
available_actions: Optional[Dict[str, ActionInfo]] = None,
enable_timeout: bool = False,
enable_tool: bool = True,
) -> str: # sourcery skip: merge-else-if-into-elif, remove-redundant-if
"""
构建回复器上下文
Args:
reply_data: 回复数据
replay_data 包含以下字段:
structured_info: 结构化信息,一般是工具调用获得的信息
reply_to: 回复对象
extra_info/extra_info_block: 额外信息
reply_to: 回复对象,格式为 "发送者:消息内容"
extra_info: 额外信息,用于补充上下文
available_actions: 可用动作
enable_timeout: 是否启用超时处理
enable_tool: 是否启用工具调用
Returns:
str: 构建好的上下文
@@ -800,15 +840,14 @@ class DefaultReplyer:
async def build_prompt_rewrite_context(
self,
reply_data: Dict[str, Any],
raw_reply: str,
reason: str,
reply_to: str,
) -> str:
chat_stream = self.chat_stream
chat_id = chat_stream.stream_id
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)
# 添加情绪状态获取
@@ -835,7 +874,7 @@ class DefaultReplyer:
# 并行执行2个构建任务
expression_habits_block, relation_info = await asyncio.gather(
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)
@@ -938,6 +977,30 @@ class DefaultReplyer:
display_message=display_message,
)
async def llm_generate_content(self, prompt: str) -> str:
with Timer("LLM生成", {}): # 内部计时器,可选保留
# 加权随机选择一个模型配置
selected_model_config = self._select_weighted_model_config()
logger.info(
f"使用模型生成回复: {selected_model_config.get('name', 'N/A')} (选中概率: {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:
"""
@@ -996,9 +1059,7 @@ async def get_prompt_info(message: str, threshold: float):
logger.debug(f"获取知识库内容耗时: {(end_time - start_time):.3f}")
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
# 格式化知识信息
formatted_prompt_info = f"你有以下这些**知识**\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n"
return formatted_prompt_info
return f"你有以下这些**知识**\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n"
else:
logger.debug("从LPMM知识库获取知识失败可能是从未导入过知识返回空知识...")
return ""

View File

@@ -851,7 +851,7 @@ class LLMRequest:
def _default_response_handler(
self, result: dict, user_id: str = "system", request_type: str = None, endpoint: str = "/chat/completions"
) -> Tuple:
):
"""默认响应解析"""
if "choices" in result and result["choices"]:
message = result["choices"][0]["message"]

View File

@@ -46,7 +46,6 @@ from .apis import (
person_api,
plugin_manage_api,
send_api,
utils_api,
register_plugin,
get_logger,
)
@@ -68,7 +67,6 @@ __all__ = [
"person_api",
"plugin_manage_api",
"send_api",
"utils_api",
"register_plugin",
"get_logger",
# 基础类

View File

@@ -17,7 +17,6 @@ from src.plugin_system.apis import (
person_api,
plugin_manage_api,
send_api,
utils_api,
)
from .logging_api import get_logger
from .plugin_register_api import register_plugin
@@ -35,7 +34,6 @@ __all__ = [
"person_api",
"plugin_manage_api",
"send_api",
"utils_api",
"get_logger",
"register_plugin",
]

View File

@@ -210,7 +210,7 @@ class ChatManager:
chat_stream: 聊天流对象
Returns:
Dict[str, Any]: 聊天流信息字典
Dict ({str: Any}): 聊天流信息字典
Raises:
TypeError: 如果 chat_stream 不是 ChatStream 类型
@@ -285,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)
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)
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)
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获取聊天流的便捷函数"""
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获取私聊流的便捷函数"""
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)
def get_stream_info(chat_stream: ChatStream):
def get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]:
"""获取聊天流信息的便捷函数"""
return ChatManager.get_stream_info(chat_stream)
def get_streams_summary():
def get_streams_summary() -> Dict[str, int]:
"""获取聊天流统计摘要的便捷函数"""
return ChatManager.get_streams_summary()

View File

@@ -10,7 +10,6 @@
from typing import Any
from src.common.logger import get_logger
from src.config.config import global_config
from src.person_info.person_info import get_person_info_manager
logger = get_logger("config_api")
@@ -26,7 +25,7 @@ def get_global_config(key: str, default: Any = None) -> Any:
插件应使用此方法读取全局配置,以保证只读和隔离性。
Args:
key: 命名空间式配置键名,支持嵌套访问,如 "section.subsection.key",大小写敏感
key: 命名空间式配置键名,使用嵌套访问,如 "section.subsection.key",大小写敏感
default: 如果配置不存在时返回的默认值
Returns:
@@ -76,50 +75,3 @@ def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any
except Exception as e:
logger.warning(f"[ConfigAPI] 获取插件配置 {key} 失败: {e}")
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

View File

@@ -152,10 +152,7 @@ async def db_query(
except DoesNotExist:
# 记录不存在
if query_type == "get" and single_result:
return None
return []
return None if query_type == "get" and single_result else []
except Exception as e:
logger.error(f"[DatabaseAPI] 数据库操作出错: {e}")
traceback.print_exc()
@@ -170,7 +167,8 @@ async def db_query(
async def db_save(
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会先尝试查找匹配的记录进行更新
@@ -203,10 +201,9 @@ async def db_save(
try:
# 如果提供了key_field和key_value尝试更新现有记录
if key_field and key_value is not None:
# 查找现有记录
existing_records = list(model_class.select().where(getattr(model_class, key_field) == key_value).limit(1))
if existing_records:
if existing_records := list(
model_class.select().where(getattr(model_class, key_field) == key_value).limit(1)
):
# 更新现有记录
existing_record = existing_records[0]
for field, value in data.items():
@@ -244,8 +241,8 @@ async def db_get(
Args:
model_class: Peewee模型类
filters: 过滤条件,字段名和值的字典
order_by: 排序字段,前缀'-'表示降序,例如'-time'表示按时间字段即time字段降序
limit: 结果数量限制
order_by: 排序字段,前缀'-'表示降序,例如'-time'表示按时间字段即time字段降序
single_result: 是否只返回单个结果如果为True则返回单个记录字典或None否则返回记录字典列表或空列表
Returns:
@@ -310,7 +307,7 @@ async def store_action_info(
thinking_id: str = "",
action_data: Optional[dict] = None,
action_name: str = "",
) -> Union[Dict[str, Any], None]:
) -> Optional[Dict[str, Any]]:
"""存储动作信息到数据库
将Action执行的相关信息保存到ActionRecords表中用于后续的记忆和上下文构建。

View File

@@ -65,14 +65,14 @@ async def get_by_description(description: str) -> Optional[Tuple[str, str, str]]
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:
count: 要获取的表情包数量默认为1
Returns:
Optional[List[Tuple[str, str, str]]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,如果失败则为None
List[Tuple[str, str, str]]: 包含(base64编码, 表情包描述, 随机情感标签)的元组列表,失败则返回空列表
Raises:
TypeError: 如果count不是整数类型
@@ -94,13 +94,13 @@ async def get_random(count: Optional[int] = 1) -> Optional[List[Tuple[str, str,
if not all_emojis:
logger.warning("[EmojiAPI] 没有可用的表情包")
return None
return []
# 过滤有效表情包
valid_emojis = [emoji for emoji in all_emojis if not emoji.is_deleted]
if not valid_emojis:
logger.warning("[EmojiAPI] 没有有效的表情包")
return None
return []
if len(valid_emojis) < count:
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:
logger.warning("[EmojiAPI] 随机获取表情包失败,没有一个可以成功处理")
return None
return []
logger.info(f"[EmojiAPI] 成功获取 {len(results)} 个随机表情包")
return results
except Exception as e:
logger.error(f"[EmojiAPI] 获取随机表情包失败: {e}")
return None
return []
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 = []
for emoji_obj in all_emojis:
if not emoji_obj.is_deleted and emotion.lower() in [e.lower() for e in emoji_obj.emotion]:
matching_emojis.append(emoji_obj)
matching_emojis.extend(
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:
logger.warning(f"[EmojiAPI] 未找到匹配情感 '{emotion}' 的表情包")
return None
@@ -256,10 +257,11 @@ def get_descriptions() -> List[str]:
emoji_manager = get_emoji_manager()
descriptions = []
for emoji_obj in emoji_manager.emoji_objects:
if not emoji_obj.is_deleted and emoji_obj.description:
descriptions.append(emoji_obj.description)
descriptions.extend(
emoji_obj.description
for emoji_obj in emoji_manager.emoji_objects
if not emoji_obj.is_deleted and emoji_obj.description
)
return descriptions
except Exception as e:
logger.error(f"[EmojiAPI] 获取表情包描述失败: {e}")

View File

@@ -84,18 +84,23 @@ async def generate_reply(
enable_chinese_typo: bool = True,
return_prompt: bool = False,
model_configs: Optional[List[Dict[str, Any]]] = None,
request_type: str = "",
enable_timeout: bool = False,
request_type: str = "generator_api",
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
"""生成回复
Args:
chat_stream: 聊天流对象(优先)
chat_id: 聊天ID备用
action_data: 动作数据
action_data: 动作数据向下兼容包含reply_to和extra_info
reply_to: 回复对象,格式为 "发送者:消息内容"
extra_info: 额外信息,用于补充上下文
available_actions: 可用动作
enable_tool: 是否启用工具调用
enable_splitter: 是否启用消息分割器
enable_chinese_typo: 是否启用错字生成器
return_prompt: 是否返回提示词
model_configs: 模型配置列表
request_type: 请求类型可选记录LLM使用
Returns:
Tuple[bool, List[Tuple[str, Any]], Optional[str]]: (是否成功, 回复集合, 提示词)
"""
@@ -118,7 +123,6 @@ async def generate_reply(
reply_to=reply_to,
extra_info=extra_info,
available_actions=available_actions,
enable_timeout=enable_timeout,
enable_tool=enable_tool,
)
reply_set = []
@@ -151,15 +155,24 @@ async def rewrite_reply(
enable_splitter: bool = True,
enable_chinese_typo: bool = True,
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:
chat_stream: 聊天流对象(优先)
reply_data: 回复数据
reply_data: 回复数据字典(向下兼容备用,当其他参数缺失时从此获取)
chat_id: 聊天ID备用
enable_splitter: 是否启用消息分割器
enable_chinese_typo: 是否启用错字生成器
model_configs: 模型配置列表
raw_reply: 原始回复内容
reason: 回复原因
reply_to: 回复对象
return_prompt: 是否返回提示词
Returns:
Tuple[bool, List[Tuple[str, Any]]]: (是否成功, 回复集合)
@@ -169,12 +182,23 @@ async def rewrite_reply(
replyer = get_replyer(chat_stream, chat_id, model_configs=model_configs)
if not replyer:
logger.error("[GeneratorAPI] 无法获取回复器")
return False, []
return False, [], None
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 = []
if content:
reply_set = await process_human_text(content, enable_splitter, enable_chinese_typo)
@@ -184,14 +208,14 @@ async def rewrite_reply(
else:
logger.warning("[GeneratorAPI] 重写回复失败")
return success, reply_set
return success, reply_set, prompt if return_prompt else None
except ValueError as ve:
raise ve
except Exception as 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]]:
@@ -217,3 +241,27 @@ async def process_human_text(content: str, enable_splitter: bool, enable_chinese
except Exception as e:
logger.error(f"[GeneratorAPI] 处理人形文本时出错: {e}")
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

View File

@@ -54,7 +54,7 @@ def get_available_models() -> Dict[str, Any]:
async def generate_with_model(
prompt: str, model_config: Dict[str, Any], request_type: str = "plugin.generate", **kwargs
) -> Tuple[bool, str, str, str]:
) -> Tuple[bool, str]:
"""使用指定模型生成内容
Args:
@@ -73,10 +73,11 @@ async def generate_with_model(
llm_request = LLMRequest(model=model_config, request_type=request_type, **kwargs)
response, (reasoning, model_name) = await llm_request.generate_response_async(prompt)
return True, response, reasoning, model_name
# TODO: 复活这个_
response, _ = await llm_request.generate_response_async(prompt)
return True, response
except Exception as e:
error_msg = f"生成内容时出错: {str(e)}"
logger.error(f"[LLMAPI] {error_msg}")
return False, error_msg, "", ""
return False, error_msg

View File

@@ -207,7 +207,7 @@ def get_random_chat_messages(
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]]:
"""
获取指定用户在所有聊天中指定时间范围内的消息
@@ -287,7 +287,7 @@ def get_messages_before_time_in_chat(
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)
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:
"""
计算指定聊天中指定用户从开始时间到结束时间的新消息数量

View File

@@ -1,10 +1,12 @@
from typing import Tuple, List
def list_loaded_plugins() -> List[str]:
"""
列出所有当前加载的插件。
Returns:
list: 当前加载的插件名称列表。
List[str]: 当前加载的插件名称列表。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
@@ -16,13 +18,34 @@ def list_registered_plugins() -> List[str]:
列出所有已注册的插件。
Returns:
list: 已注册的插件名称列表。
List[str]: 已注册的插件名称列表。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
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:
"""
卸载指定的插件。
@@ -71,6 +94,7 @@ def load_plugin(plugin_name: str) -> Tuple[bool, int]:
return plugin_manager.load_registered_plugin_classes(plugin_name)
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)
def rescan_plugin_directory() -> Tuple[int, int]:
"""
重新扫描插件目录,加载新插件。

View File

@@ -49,7 +49,7 @@ async def _send_to_target(
display_message: str = "",
typing: bool = False,
reply_to: str = "",
reply_to_platform_id: str = "",
reply_to_platform_id: Optional[str] = None,
storage_message: bool = True,
show_log: bool = True,
) -> bool:
@@ -60,8 +60,11 @@ async def _send_to_target(
content: 消息内容
stream_id: 目标流ID
display_message: 显示消息
typing: 是否显示正在输入
reply_to: 回复消息格式,如"发送者:消息内容"
typing: 是否模拟打字等待。
reply_to: 回复消息格式"发送者:消息内容"
reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!)
storage_message: 是否存储消息到数据库
show_log: 发送是否显示日志
Returns:
bool: 是否发送成功
@@ -97,6 +100,10 @@ async def _send_to_target(
anchor_message = None
if 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(
@@ -262,12 +269,22 @@ async def text_to_stream(
stream_id: 聊天流ID
typing: 是否显示正在输入
reply_to: 回复消息,格式为"发送者:消息内容"
reply_to_platform_id: 回复消息,格式为"平台:用户ID",如果不提供则自动查找(插件开发者禁用!)
storage_message: 是否存储消息到数据库
Returns:
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:
@@ -350,249 +367,3 @@ async def custom_to_stream(
storage_message=storage_message,
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
)

View File

@@ -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())

View File

@@ -208,7 +208,7 @@ class BaseAction(ABC):
return False, f"等待新消息失败: {str(e)}"
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:
"""发送文本消息
@@ -227,7 +227,6 @@ class BaseAction(ABC):
text=content,
stream_id=self.chat_id,
reply_to=reply_to,
reply_to_platform_id=reply_to_platform_id,
typing=typing,
)

View File

@@ -225,6 +225,18 @@ class PluginManager:
"""
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)
# === 私有方法 ===
# == 目录管理 ==
def _ensure_plugin_directories(self) -> None:
@@ -289,6 +301,7 @@ class PluginManager:
return False
module = module_from_spec(spec)
module.__package__ = module_name # 设置模块包名
spec.loader.exec_module(module)
logger.debug(f"插件模块加载成功: {plugin_file}")

View File

@@ -120,7 +120,7 @@ class EmojiAction(BaseAction):
logger.error(f"{self.log_prefix} 未找到'utils_small'模型配置无法调用LLM")
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"
)

View File

@@ -11,6 +11,7 @@ from src.plugin_system import (
component_manage_api,
ComponentInfo,
ComponentType,
send_api,
)
@@ -27,8 +28,15 @@ class ManagementCommand(BaseCommand):
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
):
await self.send_text("你没有权限使用插件管理命令")
await self._send_message("你没有权限使用插件管理命令")
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(" ")
if len(command_list) == 1:
await self.show_help("all")
@@ -42,7 +50,7 @@ class ManagementCommand(BaseCommand):
case "help":
await self.show_help("all")
case _:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if len(command_list) == 3:
if command_list[1] == "plugin":
@@ -56,7 +64,7 @@ class ManagementCommand(BaseCommand):
case "rescan":
await self._rescan_plugin_dirs()
case _:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
elif command_list[1] == "component":
if command_list[2] == "list":
@@ -64,10 +72,10 @@ class ManagementCommand(BaseCommand):
elif command_list[2] == "help":
await self.show_help("component")
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if len(command_list) == 4:
if command_list[1] == "plugin":
@@ -81,28 +89,28 @@ class ManagementCommand(BaseCommand):
case "add_dir":
await self._add_dir(command_list[3])
case _:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
elif command_list[1] == "component":
if command_list[2] != "list":
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if command_list[3] == "enabled":
await self._list_enabled_components()
elif command_list[3] == "disabled":
await self._list_disabled_components()
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if len(command_list) == 5:
if command_list[1] != "component":
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if command_list[2] != "list":
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if command_list[3] == "enabled":
await self._list_enabled_components(target_type=command_list[4])
@@ -111,11 +119,11 @@ class ManagementCommand(BaseCommand):
elif command_list[3] == "type":
await self._list_registered_components_by_type(command_list[4])
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if len(command_list) == 6:
if command_list[1] != "component":
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
if command_list[2] == "enable":
if command_list[3] == "global":
@@ -123,7 +131,7 @@ class ManagementCommand(BaseCommand):
elif command_list[3] == "local":
await self._locally_enable_component(command_list[4], command_list[5])
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
elif command_list[2] == "disable":
if command_list[3] == "global":
@@ -131,10 +139,10 @@ class ManagementCommand(BaseCommand):
elif command_list[3] == "local":
await self._locally_disable_component(command_list[4], command_list[5])
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
else:
await self.send_text("插件管理命令不合法")
await self._send_message("插件管理命令不合法")
return False, "命令不合法", True
return True, "命令执行完成", True
@@ -180,51 +188,51 @@ class ManagementCommand(BaseCommand):
)
case _:
return
await self.send_text(help_msg)
await self._send_message(help_msg)
async def _list_loaded_plugins(self):
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):
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):
plugin_manage_api.rescan_plugin_directory()
await self.send_text("插件目录重新扫描执行中")
await self._send_message("插件目录重新扫描执行中")
async def _load_plugin(self, plugin_name: str):
success, count = plugin_manage_api.load_plugin(plugin_name)
if success:
await self.send_text(f"插件加载成功: {plugin_name}")
await self._send_message(f"插件加载成功: {plugin_name}")
else:
if count == 0:
await self.send_text(f"插件{plugin_name}为禁用状态")
await self.send_text(f"插件加载失败: {plugin_name}")
await self._send_message(f"插件{plugin_name}为禁用状态")
await self._send_message(f"插件加载失败: {plugin_name}")
async def _unload_plugin(self, plugin_name: str):
success = await plugin_manage_api.remove_plugin(plugin_name)
if success:
await self.send_text(f"插件卸载成功: {plugin_name}")
await self._send_message(f"插件卸载成功: {plugin_name}")
else:
await self.send_text(f"插件卸载失败: {plugin_name}")
await self._send_message(f"插件卸载失败: {plugin_name}")
async def _reload_plugin(self, plugin_name: str):
success = await plugin_manage_api.reload_plugin(plugin_name)
if success:
await self.send_text(f"插件重新加载成功: {plugin_name}")
await self._send_message(f"插件重新加载成功: {plugin_name}")
else:
await self.send_text(f"插件重新加载失败: {plugin_name}")
await self._send_message(f"插件重新加载失败: {plugin_name}")
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)
await asyncio.sleep(0.5) # 防止乱序发送
if success:
await self.send_text(f"插件目录添加成功: {dir_path}")
await self._send_message(f"插件目录添加成功: {dir_path}")
else:
await self.send_text(f"插件目录添加失败: {dir_path}")
await self._send_message(f"插件目录添加失败: {dir_path}")
def _fetch_all_registered_components(self) -> List[ComponentInfo]:
all_plugin_info = component_manage_api.get_all_plugin_info()
@@ -255,29 +263,29 @@ class ManagementCommand(BaseCommand):
async def _list_all_registered_components(self):
components_info = self._fetch_all_registered_components()
if not components_info:
await self.send_text("没有注册的组件")
await self._send_message("没有注册的组件")
return
all_components_str = ", ".join(
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"):
components_info = self._fetch_all_registered_components()
if not components_info:
await self.send_text("没有注册的组件")
await self._send_message("没有注册的组件")
return
if target_type == "global":
enabled_components = [component for component in components_info if component.enabled]
if not enabled_components:
await self.send_text("没有满足条件的已启用全局组件")
await self._send_message("没有满足条件的已启用全局组件")
return
enabled_components_str = ", ".join(
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":
locally_disabled_components = self._fetch_locally_disabled_components()
enabled_components = [
@@ -286,28 +294,28 @@ class ManagementCommand(BaseCommand):
if (component.name not in locally_disabled_components and component.enabled)
]
if not enabled_components:
await self.send_text("本聊天没有满足条件的已启用组件")
await self._send_message("本聊天没有满足条件的已启用组件")
return
enabled_components_str = ", ".join(
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"):
components_info = self._fetch_all_registered_components()
if not components_info:
await self.send_text("没有注册的组件")
await self._send_message("没有注册的组件")
return
if target_type == "global":
disabled_components = [component for component in components_info if not component.enabled]
if not disabled_components:
await self.send_text("没有满足条件的已禁用全局组件")
await self._send_message("没有满足条件的已禁用全局组件")
return
disabled_components_str = ", ".join(
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":
locally_disabled_components = self._fetch_locally_disabled_components()
disabled_components = [
@@ -316,12 +324,12 @@ class ManagementCommand(BaseCommand):
if (component.name in locally_disabled_components or not component.enabled)
]
if not disabled_components:
await self.send_text("本聊天没有满足条件的已禁用组件")
await self._send_message("本聊天没有满足条件的已禁用组件")
return
disabled_components_str = ", ".join(
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):
match target_type:
@@ -332,18 +340,18 @@ class ManagementCommand(BaseCommand):
case "event_handler":
component_type = ComponentType.EVENT_HANDLER
case _:
await self.send_text(f"未知组件类型: {target_type}")
await self._send_message(f"未知组件类型: {target_type}")
return
components_info = component_manage_api.get_components_info_by_type(component_type)
if not components_info:
await self.send_text(f"没有注册的 {target_type} 组件")
await self._send_message(f"没有注册的 {target_type} 组件")
return
components_str = ", ".join(
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):
match component_type:
@@ -354,12 +362,12 @@ class ManagementCommand(BaseCommand):
case "event_handler":
target_component_type = ComponentType.EVENT_HANDLER
case _:
await self.send_text(f"未知组件类型: {component_type}")
await self._send_message(f"未知组件类型: {component_type}")
return
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:
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):
match component_type:
@@ -370,13 +378,13 @@ class ManagementCommand(BaseCommand):
case "event_handler":
target_component_type = ComponentType.EVENT_HANDLER
case _:
await self.send_text(f"未知组件类型: {component_type}")
await self._send_message(f"未知组件类型: {component_type}")
return
success = await component_manage_api.globally_disable_component(component_name, target_component_type)
if success:
await self.send_text(f"全局禁用组件成功: {component_name}")
await self._send_message(f"全局禁用组件成功: {component_name}")
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):
match component_type:
@@ -387,16 +395,16 @@ class ManagementCommand(BaseCommand):
case "event_handler":
target_component_type = ComponentType.EVENT_HANDLER
case _:
await self.send_text(f"未知组件类型: {component_type}")
await self._send_message(f"未知组件类型: {component_type}")
return
if component_manage_api.locally_enable_component(
component_name,
target_component_type,
self.message.chat_stream.stream_id,
):
await self.send_text(f"本地启用组件成功: {component_name}")
await self._send_message(f"本地启用组件成功: {component_name}")
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):
match component_type:
@@ -407,16 +415,19 @@ class ManagementCommand(BaseCommand):
case "event_handler":
target_component_type = ComponentType.EVENT_HANDLER
case _:
await self.send_text(f"未知组件类型: {component_type}")
await self._send_message(f"未知组件类型: {component_type}")
return
if component_manage_api.locally_disable_component(
component_name,
target_component_type,
self.message.chat_stream.stream_id,
):
await self.send_text(f"本地禁用组件成功: {component_name}")
await self._send_message(f"本地禁用组件成功: {component_name}")
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
@@ -430,7 +441,9 @@ class PluginManagementPlugin(BasePlugin):
"plugin": {
"enabled": ConfigField(bool, default=False, description="是否启用插件"),
"config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"),
"permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表请填写字符串形式的用户ID"),
"permission": ConfigField(
list, default=[], description="有权限使用插件管理命令的用户列表请填写字符串形式的用户ID"
),
},
}