This commit is contained in:
SengokuCola
2025-07-25 21:29:47 +08:00
12 changed files with 331 additions and 863 deletions

View File

@@ -1,27 +1,23 @@
name: Docker Build and Push
on:
# push:
# branches:
# - main
# - classical
# - dev
# tags:
# - "v*.*.*"
# - "v*"
# - "*.*.*"
# - "*.*.*-*"
workflow_dispatch: # 允许手动触发工作流
push:
branches:
- main
- classical
- dev
- dev-refactor
tags:
- "v*.*.*"
- "v*"
- "*.*.*"
- "*.*.*-*"
workflow_dispatch: # 允许手动触发工作流
# Workflow's jobs
jobs:
build-amd64:
name: Build AMD64 Image
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
@@ -74,7 +70,7 @@ jobs:
build-arm64:
name: Build ARM64 Image
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
@@ -90,11 +86,6 @@ jobs:
- name: Clone lpmm
run: git clone https://github.com/MaiM-with-u/MaiMBot-LPMM.git MaiMBot-LPMM
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
@@ -132,7 +123,7 @@ jobs:
create-manifest:
name: Create Multi-Arch Manifest
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- build-amd64
- build-arm64

View File

@@ -22,7 +22,7 @@ class ExampleAction(BaseAction):
action_name = "example_action" # 动作的唯一标识符
action_description = "这是一个示例动作" # 动作描述
activation_type = ActionActivationType.ALWAYS # 这里以 ALWAYS 为例
mode_enable = ChatMode.ALL # 这里以 ALL 为例
mode_enable = ChatMode.ALL # 一般取ALL表示在所有聊天模式下都可用
associated_types = ["text", "emoji", ...] # 关联类型
parallel_action = False # 是否允许与其他Action并行执行
action_parameters = {"param1": "参数1的说明", "param2": "参数2的说明", ...}
@@ -60,7 +60,7 @@ class ExampleAction(BaseAction):
**请知悉,对于不同的处理器,其支持的消息类型可能会有所不同。在开发时请注意。**
#### action_parameters: 该Action的参数说明。
这是一个字典键为参数名值为参数说明。这个字段可以帮助LLM理解如何使用这个Action并由LLM返回对应的参数最后传递到 Action 的 action_data 属性中。其格式与你定义的格式完全相同 **除非LLM哈气了返回了错误的内容**。
这是一个字典键为参数名值为参数说明。这个字段可以帮助LLM理解如何使用这个Action并由LLM返回对应的参数最后传递到 Action 的 **`action_data`** 属性中。其格式与你定义的格式完全相同 **除非LLM哈气了返回了错误的内容**。
---
@@ -180,6 +180,8 @@ class GreetingAction(BaseAction):
return True, "发送了问候"
```
一个完整的使用`ActionActivationType.KEYWORD`的例子请参考`plugins/hello_world_plugin`中的`ByeAction`
#### 第二层:使用决策
**在Action被激活后使用条件决定麦麦什么时候会"选择"使用这个Action**

View File

@@ -6,34 +6,6 @@
>
> 系统会根据你在代码中定义的 `config_schema` 自动生成配置文件。手动创建配置文件会破坏自动化流程,导致配置不一致、缺失注释和文档等问题。
## 📖 目录
1. [配置架构变更说明](#配置架构变更说明)
2. [配置版本管理](#配置版本管理)
3. [配置定义Schema驱动的配置系统](#配置定义schema驱动的配置系统)
4. [配置访问在Action和Command中使用配置](#配置访问在action和command中使用配置)
5. [完整示例:从定义到使用](#完整示例从定义到使用)
6. [最佳实践与注意事项](#最佳实践与注意事项)
---
## 配置架构变更说明
- **`_manifest.json`** - 负责插件的**元数据信息**(静态)
- 插件名称、版本、描述
- 作者信息、许可证
- 仓库链接、关键词、分类
- 组件列表、兼容性信息
- **`config.toml`** - 负责插件的**运行时配置**(动态)
- `enabled` - 是否启用插件
- 功能参数配置
- 组件启用开关
- 用户可调整的行为参数
---
## 配置版本管理
### 🎯 版本管理概述
@@ -103,7 +75,7 @@ config_schema = {
2. **迁移配置值** - 将旧配置文件中的值迁移到新结构中
3. **处理新增字段** - 新增的配置项使用默认值
4. **更新版本号** - `config_version` 字段自动更新为最新版本
5. **保存配置文件** - 迁移后的配置直接覆盖原文件(不保留备份)
5. **保存配置文件** - 迁移后的配置直接覆盖原文件**(不保留备份)**
### 🔧 实际使用示例
@@ -174,28 +146,13 @@ min_duration = 120
- 跳过版本检查和迁移
- 直接加载现有配置
- 新增的配置项在代码中使用默认值访问
### 📝 配置迁移日志
系统会详细记录配置迁移过程:
```log
[MutePlugin] 检测到配置版本需要更新: 当前=v1.0.0, 期望=v1.1.0
[MutePlugin] 生成新配置结构...
[MutePlugin] 迁移配置值: plugin.enabled = true
[MutePlugin] 更新配置版本: plugin.config_version = 1.1.0 (旧值: 1.0.0)
[MutePlugin] 迁移配置值: mute.min_duration = 120
[MutePlugin] 迁移配置值: mute.max_duration = 3600
[MutePlugin] 新增节: permissions
[MutePlugin] 配置文件已从 v1.0.0 更新到 v1.1.0
```
- 系统会详细记录配置迁移过程。
### ⚠️ 重要注意事项
#### 1. 版本号管理
- 当你修改 `config_schema` 时,**必须同步更新** `config_version`
- 建议使用语义化版本号 (例如:`1.0.0`, `1.1.0`, `2.0.0`)
- 配置结构的重大变更应该增加主版本号
- 使用语义化版本号 (例如:`1.0.0`, `1.1.0`, `2.0.0`)
#### 2. 迁移策略
- **保留原值优先**: 迁移时优先保留用户的原有配置值
@@ -207,45 +164,7 @@ min_duration = 120
- **不保留备份**: 迁移后直接覆盖原配置文件,不保留备份
- **失败安全**: 如果迁移过程中出现错误,会回退到原配置
---
## 配置定义Schema驱动的配置系统
### 核心理念Schema驱动的配置
在新版插件系统中,我们引入了一套 **配置Schema模式驱动** 的机制。**你不需要也不应该手动创建和维护 `config.toml` 文件**,而是通过在插件代码中 **声明配置的结构**,系统将为你完成剩下的工作。
> **⚠️ 绝对不要手动创建 config.toml 文件!**
>
> - ❌ **错误做法**:手动在插件目录下创建 `config.toml` 文件
> - ✅ **正确做法**:在插件代码中定义 `config_schema`,让系统自动生成配置文件
**核心优势:**
- **自动化 (Automation)**: 如果配置文件不存在,系统会根据你的声明 **自动生成** 一份包含默认值和详细注释的 `config.toml` 文件。
- **规范化 (Standardization)**: 所有插件的配置都遵循统一的结构,提升了可维护性。
- **自带文档 (Self-documenting)**: 配置文件中的每一项都包含详细的注释、类型说明、可选值和示例,极大地降低了用户的使用门槛。
- **健壮性 (Robustness)**: 在代码中直接定义配置的类型和默认值,减少了因配置错误导致的运行时问题。
- **易于管理 (Easy Management)**: 生成的配置文件可以方便地加入 `.gitignore`避免将个人配置如API Key提交到版本库。
### 配置生成工作流程
```mermaid
graph TD
A[编写插件代码] --> B[定义 config_schema]
B --> C[首次加载插件]
C --> D{config.toml 是否存在?}
D -->|不存在| E[系统自动生成 config.toml]
D -->|存在| F[加载现有配置文件]
E --> G[配置完成,插件可用]
F --> G
style E fill:#90EE90
style B fill:#87CEEB
style G fill:#DDA0DD
```
### 如何定义配置
## 配置定义
配置的定义在你的插件主类(继承自 `BasePlugin`)中完成,主要通过两个类属性:
@@ -257,6 +176,7 @@ graph TD
每个配置项都通过一个 `ConfigField` 对象来定义。
```python
from dataclasses import dataclass
from src.plugin_system.base.config_types import ConfigField
@dataclass
@@ -270,28 +190,21 @@ class ConfigField:
choices: Optional[List[Any]] = None # 可选值列表 (可选)
```
### 配置定义示例
### 配置示例
让我们以一个功能丰富的 `MutePlugin` 为例,看看如何定义它的配置。
```python
# src/plugins/built_in/mute_plugin/plugin.py
from src.plugin_system import BasePlugin, register_plugin
from src.plugin_system.base.config_types import ConfigField
from src.plugin_system import BasePlugin, register_plugin, ConfigField
from typing import List, Tuple, Type
@register_plugin
class MutePlugin(BasePlugin):
"""禁言插件"""
# 插件基本信息
plugin_name = "mute_plugin"
plugin_description = "群聊禁言管理插件,提供智能禁言功能"
plugin_version = "2.0.0"
plugin_author = "MaiBot开发团队"
enable_plugin = True
config_file_name = "config.toml"
# 这里是插件基本信息,略去
# 步骤1: 定义配置节的描述
config_section_descriptions = {
@@ -339,22 +252,9 @@ class MutePlugin(BasePlugin):
}
}
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
# 在这里可以通过 self.get_config() 来获取配置值
enable_smart_mute = self.get_config("components.enable_smart_mute", True)
enable_mute_command = self.get_config("components.enable_mute_command", False)
components = []
if enable_smart_mute:
components.append((SmartMuteAction.get_action_info(), SmartMuteAction))
if enable_mute_command:
components.append((MuteCommand.get_command_info(), MuteCommand))
return components
# 这里是插件方法,略去
```
### 自动生成的配置文件
`mute_plugin` 首次加载且其目录中不存在 `config.toml` 时,系统会自动创建以下文件:
```toml
@@ -413,317 +313,24 @@ prefix = "[MutePlugin]"
---
## 配置访问在Action和Command中使用配置
## 配置访问
### 问题描述
如果你想要在你的组件中访问配置,可以通过组件内置的 `get_config()` 方法访问配置。
在插件开发中,你可能遇到这样的问题
- 想要在Action或Command中访问插件配置
### ✅ 解决方案
**直接使用 `self.get_config()` 方法!**
系统已经自动为你处理了配置传递,你只需要通过组件内置的 `get_config` 方法访问配置即可。
### 📖 快速示例
#### 在Action中访问配置
其参数为一个命名空间化的字符串。以上面的 `MutePlugin` 为例,你可以这样访问配置
```python
from src.plugin_system import BaseAction
class MyAction(BaseAction):
async def execute(self):
# 方法1: 获取配置值(带默认值)
api_key = self.get_config("api.key", "default_key")
timeout = self.get_config("api.timeout", 30)
# 方法2: 支持嵌套键访问
log_level = self.get_config("advanced.logging.level", "INFO")
# 方法3: 直接访问顶层配置
enable_feature = self.get_config("features.enable_smart", False)
# 使用配置值
if enable_feature:
await self.send_text(f"API密钥: {api_key}")
return True, "配置访问成功"
enable_smart_mute = self.get_config("components.enable_smart_mute", True)
```
#### 在Command中访问配置
```python
from src.plugin_system import BaseCommand
class MyCommand(BaseCommand):
async def execute(self):
# 使用方式与Action完全相同
welcome_msg = self.get_config("messages.welcome", "欢迎!")
max_results = self.get_config("search.max_results", 10)
# 根据配置执行不同逻辑
if self.get_config("features.debug_mode", False):
await self.send_text(f"调试模式已启用,最大结果数: {max_results}")
await self.send_text(welcome_msg)
return True, "命令执行完成"
```
### 🔧 API方法详解
#### 1. `get_config(key, default=None)`
获取配置值,支持嵌套键访问:
```python
# 简单键
value = self.get_config("timeout", 30)
# 嵌套键(用点号分隔)
value = self.get_config("database.connection.host", "localhost")
value = self.get_config("features.ai.model", "gpt-3.5-turbo")
```
#### 2. 类型安全的配置访问
```python
# 确保正确的类型
max_retries = self.get_config("api.max_retries", 3)
if not isinstance(max_retries, int):
max_retries = 3 # 使用安全的默认值
# 布尔值配置
debug_mode = self.get_config("features.debug_mode", False)
if debug_mode:
# 调试功能逻辑
pass
```
#### 3. 配置驱动的组件行为
```python
class ConfigDrivenAction(BaseAction):
async def execute(self):
# 根据配置决定激活行为
activation_config = {
"use_keywords": self.get_config("activation.use_keywords", True),
"use_llm": self.get_config("activation.use_llm", False),
"keywords": self.get_config("activation.keywords", []),
}
# 根据配置调整功能
features = {
"enable_emoji": self.get_config("features.enable_emoji", True),
"enable_llm_reply": self.get_config("features.enable_llm_reply", False),
"max_length": self.get_config("output.max_length", 200),
}
# 使用配置执行逻辑
if features["enable_llm_reply"]:
# 使用LLM生成回复
pass
else:
# 使用模板回复
pass
return True, "配置驱动执行完成"
```
### 🔄 配置传递机制
系统自动处理配置传递,无需手动操作:
1. **插件初始化**`BasePlugin`加载`config.toml``self.config`
2. **组件注册** → 系统记录插件配置
3. **组件实例化** → 自动传递`plugin_config`参数给Action/Command
4. **配置访问** → 组件通过`self.get_config()`直接访问配置
---
## 完整示例:从定义到使用
### 插件定义
```python
from src.plugin_system.base.config_types import ConfigField
@register_plugin
class GreetingPlugin(BasePlugin):
"""问候插件完整示例"""
plugin_name = "greeting_plugin"
plugin_description = "智能问候插件,展示配置定义和访问的完整流程"
plugin_version = "1.0.0"
config_file_name = "config.toml"
# 配置节描述
config_section_descriptions = {
"plugin": "插件启用配置",
"greeting": "问候功能配置",
"features": "功能开关配置",
"messages": "消息模板配置"
}
# 配置Schema定义
config_schema = {
"plugin": {
"enabled": ConfigField(type=bool, default=True, description="是否启用插件")
},
"greeting": {
"template": ConfigField(
type=str,
default="你好,{username}!欢迎使用问候插件!",
description="问候消息模板"
),
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
"enable_llm": ConfigField(type=bool, default=False, description="是否使用LLM生成个性化问候")
},
"features": {
"smart_detection": ConfigField(type=bool, default=True, description="是否启用智能检测"),
"random_greeting": ConfigField(type=bool, default=False, description="是否使用随机问候语"),
"max_greetings_per_hour": ConfigField(type=int, default=5, description="每小时最大问候次数")
},
"messages": {
"custom_greetings": ConfigField(
type=list,
default=["你好!", "嗨!", "欢迎!"],
description="自定义问候语列表"
),
"error_message": ConfigField(
type=str,
default="问候功能暂时不可用",
description="错误时显示的消息"
)
}
}
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
"""根据配置动态注册组件"""
components = []
# 根据配置决定是否注册组件
if self.get_config("plugin.enabled", True):
components.append((SmartGreetingAction.get_action_info(), SmartGreetingAction))
components.append((GreetingCommand.get_command_info(), GreetingCommand))
return components
```
### Action组件使用配置
```python
class SmartGreetingAction(BaseAction):
"""智能问候Action - 展示配置访问"""
focus_activation_type = ActionActivationType.KEYWORD
normal_activation_type = ActionActivationType.KEYWORD
activation_keywords = ["你好", "hello", "hi"]
async def execute(self) -> Tuple[bool, str]:
"""执行智能问候,大量使用配置"""
try:
# 检查插件是否启用
if not self.get_config("plugin.enabled", True):
return False, "插件已禁用"
# 获取问候配置
template = self.get_config("greeting.template", "你好,{username}")
enable_emoji = self.get_config("greeting.enable_emoji", True)
enable_llm = self.get_config("greeting.enable_llm", False)
# 获取功能配置
smart_detection = self.get_config("features.smart_detection", True)
random_greeting = self.get_config("features.random_greeting", False)
max_per_hour = self.get_config("features.max_greetings_per_hour", 5)
# 获取消息配置
custom_greetings = self.get_config("messages.custom_greetings", [])
error_message = self.get_config("messages.error_message", "问候功能不可用")
# 根据配置执行不同逻辑
username = self.action_data.get("username", "用户")
if random_greeting and custom_greetings:
# 使用随机自定义问候语
import random
greeting_msg = random.choice(custom_greetings)
elif enable_llm:
# 使用LLM生成个性化问候
greeting_msg = await self._generate_llm_greeting(username)
else:
# 使用模板问候
greeting_msg = template.format(username=username)
# 发送问候消息
await self.send_text(greeting_msg)
# 根据配置发送表情
if enable_emoji:
await self.send_emoji("😊")
return True, f"向{username}发送了问候"
except Exception as e:
# 使用配置的错误消息
await self.send_text(self.get_config("messages.error_message", "出错了"))
return False, f"问候失败: {str(e)}"
async def _generate_llm_greeting(self, username: str) -> str:
"""根据配置使用LLM生成问候语"""
# 这里可以进一步使用配置来定制LLM行为
llm_style = self.get_config("greeting.llm_style", "friendly")
# ... LLM调用逻辑
return f"你好 {username}!很高兴见到你!"
```
### Command组件使用配置
```python
class GreetingCommand(BaseCommand):
"""问候命令 - 展示配置访问"""
command_pattern = r"^/greet(?:\s+(?P<username>\w+))?$"
command_help = "发送问候消息"
command_examples = ["/greet", "/greet Alice"]
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行问候命令"""
# 检查功能是否启用
if not self.get_config("plugin.enabled", True):
await self.send_text("问候功能已禁用")
return False, "功能禁用"
# 获取用户名
username = self.matched_groups.get("username", "用户")
# 根据配置选择问候方式
if self.get_config("features.random_greeting", False):
custom_greetings = self.get_config("messages.custom_greetings", ["你好!"])
import random
greeting = random.choice(custom_greetings)
else:
template = self.get_config("greeting.template", "你好,{username}")
greeting = template.format(username=username)
# 发送问候
await self.send_text(greeting)
# 根据配置发送表情
if self.get_config("greeting.enable_emoji", True):
await self.send_text("😊")
return True, "问候发送成功"
```
如果尝试访问了一个不存在的配置项,系统会自动返回默认值(你传递的)或者 `None`
---
## 最佳实践与注意事项
### 配置定义最佳实践
> **🚨 核心原则:永远不要手动创建 config.toml 文件!**
**🚨 核心原则:永远不要手动创建 config.toml 文件!**
1. **🔥 绝不手动创建配置文件**: **任何时候都不要手动创建 `config.toml` 文件**!必须通过在 `plugin.py` 中定义 `config_schema` 让系统自动生成。
-**禁止**`touch config.toml`、手动编写配置文件
@@ -738,75 +345,3 @@ class GreetingCommand(BaseCommand):
5. **gitignore**: 将 `plugins/*/config.toml``src/plugins/built_in/*/config.toml` 加入 `.gitignore`,以避免提交个人敏感信息。
6. **配置文件只供修改**: 自动生成的 `config.toml` 文件只应该被用户**修改**,而不是从零创建。
### 配置访问最佳实践
#### 1. 总是提供默认值
```python
# ✅ 好的做法
timeout = self.get_config("api.timeout", 30)
# ❌ 避免这样做
timeout = self.get_config("api.timeout") # 可能返回None
```
#### 2. 验证配置类型
```python
# 获取配置后验证类型
max_items = self.get_config("list.max_items", 10)
if not isinstance(max_items, int) or max_items <= 0:
max_items = 10 # 使用安全的默认值
```
#### 3. 缓存复杂配置解析
```python
class MyAction(BaseAction):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 在初始化时解析复杂配置,避免重复解析
self._api_config = self._parse_api_config()
def _parse_api_config(self):
return {
'key': self.get_config("api.key", ""),
'timeout': self.get_config("api.timeout", 30),
'retries': self.get_config("api.max_retries", 3)
}
```
#### 4. 配置驱动的组件注册
```python
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
"""根据配置动态注册组件"""
components = []
# 从配置获取组件启用状态
enable_action = self.get_config("components.enable_action", True)
enable_command = self.get_config("components.enable_command", True)
if enable_action:
components.append((MyAction.get_action_info(), MyAction))
if enable_command:
components.append((MyCommand.get_command_info(), MyCommand))
return components
```
### 🎉 总结
现在你掌握了插件配置的完整流程:
1. **定义配置**: 在插件中使用 `config_schema` 定义配置结构
2. **访问配置**: 在组件中使用 `self.get_config("key", default_value)` 访问配置
3. **自动生成**: 系统自动生成带注释的配置文件
4. **动态行为**: 根据配置动态调整插件行为
> **🚨 最后强调:任何时候都不要手动创建 config.toml 文件!**
>
> 让系统根据你的 `config_schema` 自动生成配置文件,这是插件系统的核心设计原则。
不需要继承`BasePlugin`,不需要复杂的配置传递,不需要手动创建配置文件,组件内置的`get_config`方法和自动化的配置生成机制已经为你准备好了一切!

View File

@@ -1,20 +1,14 @@
# 🚀 快速开始指南
本指南将带你用5分钟时间从零开始创建一个功能完整的MaiCore插件。
本指南将带你从零开始创建一个功能完整的MaiCore插件。
## 📖 概述
这个指南将带你快速创建你的第一个MaiCore插件。我们将创建一个简单的问候插件展示插件系统的基本概念。无需阅读其他文档,跟着本指南就能完成!
这个指南将带你快速创建你的第一个MaiCore插件。我们将创建一个简单的问候插件展示插件系统的基本概念。
## 🎯 学习目标
以下代码都在我们的`plugins/hello_world_plugin/`目录下。
- 理解插件的基本结构
- 从最简单的插件开始,循序渐进
- 学会创建Action组件智能动作
- 学会创建Command组件命令响应
- 掌握配置Schema定义和配置文件自动生成可选
## 📂 准备工作
### 📂 准备工作
确保你已经:
@@ -26,16 +20,29 @@
### 1. 创建插件目录
在项目根目录的 `plugins/` 文件夹下创建你的插件目录,目录名与插件名保持一致:
在项目根目录的 `plugins/` 文件夹下创建你的插件目录
可以用以下命令快速创建:
这里我们创建一个名为 `hello_world_plugin` 的目录
```bash
mkdir plugins/hello_world_plugin
cd plugins/hello_world_plugin
### 2. 创建`_manifest.json`文件
在插件目录下面创建一个 `_manifest.json` 文件,内容如下:
```json
{
"manifest_version": 1,
"name": "Hello World 插件",
"version": "1.0.0",
"description": "一个简单的 Hello World 插件",
"author": {
"name": "你的名字"
}
}
```
### 2. 创建最简单的插件
有关 `_manifest.json` 的详细说明,请参考 [Manifest文件指南](./manifest-guide.md)。
### 3. 创建最简单的插件
让我们从最基础的开始!创建 `plugin.py` 文件:
@@ -43,34 +50,33 @@ cd plugins/hello_world_plugin
from typing import List, Tuple, Type
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
# ===== 插件注册 =====
@register_plugin
@register_plugin # 注册插件
class HelloWorldPlugin(BasePlugin):
"""Hello World插件 - 你的第一个MaiCore插件"""
# 插件基本信息(必须填写)
# 以下是插件基本信息和方法(必须填写)
plugin_name = "hello_world_plugin"
plugin_description = "我的第一个MaiCore插件"
plugin_version = "1.0.0"
plugin_author = "你的名字"
enable_plugin = True # 启用插件
dependencies = [] # 插件依赖列表(目前为空)
python_dependencies = [] # Python依赖列表目前为空
config_file_name = "config.toml" # 配置文件名
config_schema = {} # 配置文件模式(目前为空)
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: # 获取插件组件
"""返回插件包含的组件列表(目前是空的)"""
return []
```
🎉 **恭喜你刚刚创建了一个最简单但完整的MaiCore插件**
🎉 恭喜你刚刚创建了一个最简单但完整的MaiCore插件
**解释一下这些代码:**
- 首先我们在plugin.py中定义了一个HelloWorldPulgin插件类继承自 `BasePlugin` ,提供基本功能。
- 首先,我们在`plugin.py`中定义了一个HelloWorldPlugin插件类继承自 `BasePlugin` ,提供基本功能。
- 通过给类加上,`@register_plugin` 装饰器,我们告诉系统"这是一个插件"
- `plugin_name` 等是插件的基本信息,必须填写**此部分必须与目录名称相同,否则插件无法使用**
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何action动作或者command(指令),是空的
- `plugin_name` 等是插件的基本信息,必须填写
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何 Action, Command 或者 EventHandler所以返回空列表。
### 3. 测试基础插件
### 4. 测试基础插件
现在就可以测试这个插件了启动MaiCore
@@ -80,7 +86,7 @@ class HelloWorldPlugin(BasePlugin):
![1750326700269](image/quick-start/1750326700269.png)
### 4. 添加第一个功能问候Action
### 5. 添加第一个功能问候Action
现在我们要给插件加入一个有用的功能我们从最好玩的Action做起
@@ -107,40 +113,34 @@ class HelloAction(BaseAction):
# === 基本信息(必须填写)===
action_name = "hello_greeting"
action_description = "向用户发送问候消息"
activation_type = ActionActivationType.ALWAYS # 始终激活
# === 功能描述(必须填写)===
action_parameters = {
"greeting_message": "要发送的问候消息"
}
action_require = [
"需要发送友好问候时使用",
"当有人向你问好时使用",
"当你遇见没有见过的人时使用"
]
action_parameters = {"greeting_message": "要发送的问候消息"}
action_require = ["要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
associated_types = ["text"]
async def execute(self) -> Tuple[bool, str]:
"""执行问候动作 - 这是核心功能"""
# 发送问候消息
greeting_message = self.action_data.get("greeting_message","")
message = "嗨!很开心见到你!😊" + greeting_message
greeting_message = self.action_data.get("greeting_message", "")
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
message = base_message + greeting_message
await self.send_text(message)
return True, "发送了问候消息"
# ===== 插件注册 =====
@register_plugin
class HelloWorldPlugin(BasePlugin):
"""Hello World插件 - 你的第一个MaiCore插件"""
# 插件基本信息
plugin_name = "hello_world_plugin"
plugin_description = "我的第一个MaiCore插件包含问候功能"
plugin_version = "1.0.0"
plugin_author = "你的名字"
enable_plugin = True
dependencies = []
python_dependencies = []
config_file_name = "config.toml"
config_schema = {}
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
"""返回插件包含的组件列表"""
@@ -150,13 +150,17 @@ class HelloWorldPlugin(BasePlugin):
]
```
**新增内容解释:**
**解释一下这些代码**
- `HelloAction`一个Action组件MaiCore可能会选择使用它
- `HelloAction`我们定义的问候动作类,继承自 `BaseAction`,并实现了核心功能。
-`HelloWorldPlugin` 中,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `HelloAction` 注册为插件的一个组件。
- 这样一来当插件被加载时问候动作也会被一并加载并可以在MaiCore中使用。
- `execute()` 函数是Action的核心定义了当Action被MaiCore选择后具体要做什么
- `self.send_text()` 是发送文本消息的便捷方法
### 5. 测试问候功能
Action 组件中有关`activation_type``action_parameters``action_require``associated_types` 等的详细说明请参考 [Action组件指南](./action-components.md)。
### 6. 测试问候Action
重启MaiCore然后在聊天中发送任意消息比如
@@ -174,96 +178,17 @@ MaiCore可能会选择使用你的问候Action发送回复
> **💡 小提示**MaiCore会智能地决定什么时候使用它。如果没有立即看到效果多试几次不同的消息。
🎉 **太棒了!你的插件已经有实际功能了!**
🎉 太棒了!你的插件已经有实际功能了!
### 5.5. 了解激活系统(重要概念)
Action固然好用简单但是现在有个问题当用户加载了非常多的插件添加了很多自定义ActionLLM需要选择的Action也会变多
而不断增多的Action会加大LLM的消耗和负担降低Action使用的精准度。而且我们并不需要LLM在所有时候都考虑所有Action
例如,当群友只是在进行正常的聊天,就没有必要每次都考虑是否要选择“禁言”动作,这不仅影响决策速度,还会增加消耗。
那有什么办法能够让Action有选择的加入MaiCore的决策池呢
**什么是激活系统?**
激活系统决定了什么时候你的Action会被MaiCore"考虑"使用:
- **`ActionActivationType.ALWAYS`** - 总是可用(默认值)
- **`ActionActivationType.KEYWORD`** - 只有消息包含特定关键词时才可用
- **`ActionActivationType.PROBABILITY`** - 根据概率随机可用
- **`ActionActivationType.NEVER`** - 永不可用(用于调试)
> **💡 使用提示**
>
> - 推荐使用枚举类型(如 `ActionActivationType.ALWAYS`),有代码提示和类型检查
> - 也可以直接使用字符串(如 `"always"`),系统都支持
### 5.6. 进阶:尝试关键词激活(可选)
现在让我们尝试一个更精确的激活方式添加一个只在用户说特定关键词时才激活的Action
```python
# 在HelloAction后面添加这个新Action
class ByeAction(BaseAction):
"""告别Action - 只在用户说再见时激活"""
action_name = "bye_greeting"
action_description = "向用户发送告别消息"
# 使用关键词激活
focus_activation_type = ActionActivationType.KEYWORD
normal_activation_type = ActionActivationType.KEYWORD
# 关键词设置
activation_keywords = ["再见", "bye", "88", "拜拜"]
keyword_case_sensitive = False
action_parameters = {"bye_message": "要发送的告别消息"}
action_require = [
"用户要告别时使用",
"当有人要离开时使用",
"当有人和你说再见时使用",
]
associated_types = ["text"]
async def execute(self) -> Tuple[bool, str]:
bye_message = self.action_data.get("bye_message","")
message = "再见!期待下次聊天!👋" + bye_message
await self.send_text(message)
return True, "发送了告别消息"
```
然后在插件注册中添加这个Action
```python
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
return [
(HelloAction.get_action_info(), HelloAction),
(ByeAction.get_action_info(), ByeAction), # 添加告别Action
]
```
现在测试:发送"再见"应该会触发告别Action
**关键词激活的特点:**
- 更精确:只在包含特定关键词时才会被考虑
- 更可预测:用户知道说什么会触发什么功能
- 更适合:特定场景或命令式的功能
### 6. 添加第二个功能时间查询Command
### 7. 添加第二个功能时间查询Command
现在让我们添加一个Command组件。Command和Action不同它是直接响应用户命令的
Command是最简单最直接的不由LLM判断选择使用
Command是最简单最直接的不由LLM判断选择使用
```python
# 在现有代码基础上添加Command组件
# ===== Command组件 =====
import datetime
from src.plugin_system import BaseCommand
#导入Command基类
@@ -275,16 +200,11 @@ class TimeCommand(BaseCommand):
# === 命令设置(必须填写)===
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
command_help = "查询当前时间"
command_examples = ["/time"]
intercept_message = True # 拦截消息,不让其他组件处理
async def execute(self) -> Tuple[bool, str]:
async def execute(self) -> Tuple[bool, Optional[str], bool]:
"""执行时间查询"""
import datetime
# 获取当前时间
time_format = self.get_config("time.format", "%Y-%m-%d %H:%M:%S")
time_format: str = "%Y-%m-%d %H:%M:%S"
now = datetime.datetime.now()
time_str = now.strftime(time_format)
@@ -292,36 +212,37 @@ class TimeCommand(BaseCommand):
message = f"⏰ 当前时间:{time_str}"
await self.send_text(message)
return True, f"显示了当前时间: {time_str}"
# ===== 插件注册 =====
return True, f"显示了当前时间: {time_str}", True
@register_plugin
class HelloWorldPlugin(BasePlugin):
"""Hello World插件 - 你的第一个MaiCore插件"""
# 插件基本信息
plugin_name = "hello_world_plugin"
plugin_description = "我的第一个MaiCore插件包含问候和时间查询功能"
plugin_version = "1.0.0"
plugin_author = "你的名字"
enable_plugin = True
dependencies = []
python_dependencies = []
config_file_name = "config.toml"
config_schema = {}
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
return [
(HelloAction.get_action_info(), HelloAction),
(ByeAction.get_action_info(), ByeAction),
(TimeCommand.get_command_info(), TimeCommand),
]
```
同样的,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `TimeCommand` 注册为插件的一个组件。
**Command组件解释**
- Command是直接响应用户命令的组件
- `command_pattern` 使用正则表达式匹配用户输入
- `^/time$` 表示精确匹配 "/time"
- `intercept_message = True` 表示处理完命令后不再让其他组件处理
### 7. 测试时间查询功能
有关 Command 组件的更多信息,请参考 [Command组件指南](./command-components.md)。
### 8. 测试时间查询Command
重启MaiCore发送命令
@@ -332,106 +253,147 @@ class HelloWorldPlugin(BasePlugin):
你应该会收到回复:
```
⏰ 当前时间2024-01-01 12:30:45
⏰ 当前时间2024-01-01 12:00:00
```
🎉 **太棒了!现在你的插件有3个功能了**
🎉 太棒了!现在你已经了解了基本的 Action 和 Command 组件的使用方法。你可以根据自己的需求,继续扩展插件的功能,添加更多的 Action 和 Command 组件,让你的插件更加丰富和强大!
### 8. 添加配置文件(可选进阶)
---
如果你想让插件更加灵活,可以添加配置支持。
## 进阶教程
如果你想让插件更加灵活和强大,可以参考接下来的进阶教程。
### 1. 添加配置文件
想要为插件添加配置文件吗?让我们一起来配置`config_schema`属性!
> **🚨 重要不要手动创建config.toml文件**
>
> 我们需要在插件代码中定义配置Schema让系统自动生成配置文件。
#### 📄 配置架构说明
在新的插件系统中,我们采用了**职责分离**的设计:
- **`_manifest.json`** - 插件元数据(名称、版本、描述、作者等)
- **`config.toml`** - 运行时配置(启用状态、功能参数等)
这样避免了信息重复,提高了维护性。
首先在插件类中定义配置Schema
```python
from src.plugin_system.base.config_types import ConfigField
from src.plugin_system import ConfigField
@register_plugin
class HelloWorldPlugin(BasePlugin):
"""Hello World插件 - 你的第一个MaiCore插件"""
plugin_name = "hello_world_plugin"
plugin_description = "我的第一个MaiCore插件包含问候和时间查询功能"
plugin_version = "1.0.0"
plugin_author = "你的名字"
enable_plugin = True
config_file_name = "config.toml" # 配置文件名
# 配置节描述
config_section_descriptions = {
"plugin": "插件启用配置",
"greeting": "问候功能配置",
"time": "时间查询配置"
}
# 插件基本信息
plugin_name: str = "hello_world_plugin" # 内部标识符
enable_plugin: bool = True
dependencies: List[str] = [] # 插件依赖列表
python_dependencies: List[str] = [] # Python包依赖列表
config_file_name: str = "config.toml" # 配置文件名
# 配置Schema定义
config_schema = {
config_schema: dict = {
"plugin": {
"enabled": ConfigField(type=bool, default=True, description="是否启用插件")
"name": ConfigField(type=str, default="hello_world_plugin", description="插件名称"),
"version": ConfigField(type=str, default="1.0.0", description="插件版本"),
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
},
"greeting": {
"message": ConfigField(
type=str,
default="嗨!很开心见到你!😊",
description="默认问候消息"
),
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号")
"message": ConfigField(type=str, default="嗨!很开心见到你!😊", description="默认问候消息"),
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用表情符号"),
},
"time": {
"format": ConfigField(
type=str,
default="%Y-%m-%d %H:%M:%S",
description="时间显示格式"
)
}
"time": {"format": ConfigField(type=str, default="%Y-%m-%d %H:%M:%S", description="时间显示格式")},
}
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
return [
(HelloAction.get_action_info(), HelloAction),
(ByeAction.get_action_info(), ByeAction),
(TimeCommand.get_command_info(), TimeCommand),
]
```
然后修改Action和Command代码让它们读取配置
这会生成一个如下的 `config.toml` 文件
```toml
# hello_world_plugin - 自动生成的配置文件
# 我的第一个MaiCore插件包含问候功能和时间查询等基础示例
# 插件基本信息
[plugin]
# 插件名称
name = "hello_world_plugin"
# 插件版本
version = "1.0.0"
# 是否启用插件
enabled = false
# 问候功能配置
[greeting]
# 默认问候消息
message = "嗨!很开心见到你!😊"
# 是否启用表情符号
enable_emoji = true
# 时间查询配置
[time]
# 时间显示格式
format = "%Y-%m-%d %H:%M:%S"
```
然后修改Action和Command代码通过 `get_config()` 方法让它们读取配置(配置的键是命名空间式的):
```python
# 在HelloAction的execute方法中
async def execute(self) -> Tuple[bool, str]:
# 从配置文件读取问候消息
greeting_message = self.action_data.get("greeting_message", "")
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
class HelloAction(BaseAction):
"""问候Action - 简单的问候动作"""
message = base_message + greeting_message
await self.send_text(message)
return True, "发送问候消息"
# === 基本信息(必须填写)===
action_name = "hello_greeting"
action_description = "向用户发送问候消息"
activation_type = ActionActivationType.ALWAYS # 始终激活
# 在TimeCommand的execute方法中
async def execute(self) -> Tuple[bool, str]:
import datetime
# === 功能描述(必须填写)===
action_parameters = {"greeting_message": "要发送的问候消息"}
action_require = ["需要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
associated_types = ["text"]
# 从配置文件读取时间格式
time_format = self.get_config("time.format", "%Y-%m-%d %H:%M:%S")
now = datetime.datetime.now()
time_str = now.strftime(time_format)
async def execute(self) -> Tuple[bool, str]:
"""执行问候动作 - 这是核心功能"""
# 发送问候消息
greeting_message = self.action_data.get("greeting_message", "")
base_message = self.get_config("greeting.message", "嗨!很开心见到你!😊")
message = base_message + greeting_message
await self.send_text(message)
message = f"⏰ 当前时间:{time_str}"
await self.send_text(message)
return True, f"显示了当前时间: {time_str}"
return True, "发送了问候消息"
class TimeCommand(BaseCommand):
"""时间查询Command - 响应/time命令"""
command_name = "time"
command_description = "查询当前时间"
# === 命令设置(必须填写)===
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
async def execute(self) -> Tuple[bool, str, bool]:
"""执行时间查询"""
import datetime
# 获取当前时间
time_format: str = self.get_config("time.format", "%Y-%m-%d %H:%M:%S") # type: ignore
now = datetime.datetime.now()
time_str = now.strftime(time_format)
# 发送时间信息
message = f"⏰ 当前时间:{time_str}"
await self.send_text(message)
return True, f"显示了当前时间: {time_str}", True
```
**配置系统工作流程:**
@@ -441,47 +403,20 @@ async def execute(self) -> Tuple[bool, str]:
3. **用户修改**: 用户可以修改生成的配置文件
4. **代码读取**: 使用 `self.get_config()` 读取配置值
**配置功能解释:**
**绝对不要手动创建 `config.toml` 文件!**
- `self.get_config()` 可以读取配置文件中的值
- 第一个参数是配置路径(用点分隔),第二个参数是默认值
- 配置文件会包含详细的注释和说明,用户可以轻松理解和修改
- **绝不要手动创建配置文件**,让系统自动生成
更详细的配置系统介绍请参考 [配置指南](./configuration-guide.md)。
### 9. 创建说明文档(可选)
### 2. 创建说明文档
创建 `README.md` 文件来说明你的插件:
你可以创建一个 `README.md` 文件,描述插件的功能和使用方法。
```markdown
# Hello World 插件
### 3. 发布到插件市场
## 概述
我的第一个MaiCore插件包含问候和时间查询功能。
如果你想让更多人使用你的插件可以将它发布到MaiCore的插件市场。
## 功能
- **问候功能**: 当用户说"你好"、"hello"、"hi"时自动回复
- **时间查询**: 发送 `/time` 命令查询当前时间
这部分请参考 [plugin-repo](https://github.com/Maim-with-u/plugin-repo) 的文档。
## 使用方法
### 问候功能
发送包含以下关键词的消息:
- "你好"
- "hello"
- "hi"
---
### 时间查询
发送命令:`/time`
## 配置文件
插件会自动生成 `config.toml` 配置文件,用户可以修改:
- 问候消息内容
- 时间显示格式
- 插件启用状态
注意:配置文件是自动生成的,不要手动创建!
```
```
```
🎉 恭喜你!你已经成功的创建了自己的插件了!

View File

@@ -12,7 +12,7 @@ from src.chat.message_receive.storage import MessageStorage
from src.chat.heart_flow.heartflow import heartflow
from src.chat.utils.utils import is_mentioned_bot_in_message
from src.chat.utils.timer_calculator import Timer
from src.chat.utils.chat_message_builder import replace_user_references_in_content
from src.chat.utils.chat_message_builder import replace_user_references_sync
from src.common.logger import get_logger
from src.person_info.relationship_manager import get_relationship_manager
from src.mood.mood_manager import mood_manager
@@ -151,10 +151,9 @@ class HeartFCMessageReceiver:
processed_plain_text = re.sub(picid_pattern, "[图片]", message.processed_plain_text)
# 应用用户引用格式替换,将回复<aaa:bbb>和@<aaa:bbb>格式转换为可读格式
processed_plain_text = replace_user_references_in_content(
processed_plain_text = replace_user_references_sync(
processed_plain_text,
message.message_info.platform,
is_async=False,
message.message_info.platform, # type: ignore
replace_bot_name=True
)

View File

@@ -224,7 +224,8 @@ class Hippocampus:
return hash((source, target))
@staticmethod
def find_topic_llm(text:str, topic_num:int|list[int]):
def find_topic_llm(text: str, topic_num: int | list[int]):
# sourcery skip: inline-immediately-returned-variable
topic_num_str = ""
if isinstance(topic_num, list):
topic_num_str = f"{topic_num[0]}-{topic_num[1]}"
@@ -319,7 +320,7 @@ class Hippocampus:
# 使用LLM提取关键词 - 根据详细文本长度分布优化topic_num计算
text_length = len(text)
topic_num:str|list[int] = None
topic_num: int | list[int] = 0
if text_length <= 5:
words = jieba.cut(text)
keywords = [word for word in words if len(word) > 1]
@@ -328,17 +329,16 @@ class Hippocampus:
logger.info(f"提取关键词: {keywords}")
return keywords
elif text_length <= 10:
topic_num = [1,3] # 6-10字符: 1个关键词 (27.18%的文本)
topic_num = [1, 3] # 6-10字符: 1个关键词 (27.18%的文本)
elif text_length <= 20:
topic_num = [2,4] # 11-20字符: 2个关键词 (22.76%的文本)
topic_num = [2, 4] # 11-20字符: 2个关键词 (22.76%的文本)
elif text_length <= 30:
topic_num = [3,5] # 21-30字符: 3个关键词 (10.33%的文本)
topic_num = [3, 5] # 21-30字符: 3个关键词 (10.33%的文本)
elif text_length <= 50:
topic_num = [4,5] # 31-50字符: 4个关键词 (9.79%的文本)
topic_num = [4, 5] # 31-50字符: 4个关键词 (9.79%的文本)
else:
topic_num = 5 # 51+字符: 5个关键词 (其余长文本)
topics_response, (reasoning_content, model_name) = await self.model_summary.generate_response_async(
self.find_topic_llm(text, topic_num)
)
@@ -1312,6 +1312,7 @@ class ParahippocampalGyrus:
return compressed_memory, similar_topics_dict
async def operation_build_memory(self):
# sourcery skip: merge-list-appends-into-extend
logger.info("------------------------------------开始构建记忆--------------------------------------")
start_time = time.time()
memory_samples = self.hippocampus.entorhinal_cortex.get_memory_sample()

View File

@@ -17,7 +17,11 @@ from src.chat.message_receive.uni_message_sender import HeartFCSender
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
from src.chat.utils.utils import get_chat_type_and_target_info
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat, replace_user_references_in_content
from src.chat.utils.chat_message_builder import (
build_readable_messages,
get_raw_msg_before_timestamp_with_chat,
replace_user_references_sync,
)
from src.chat.express.expression_selector import expression_selector
from src.chat.knowledge.knowledge_lib import qa_manager
from src.chat.memory_system.memory_activator import MemoryActivator
@@ -30,6 +34,7 @@ from src.plugin_system.base.component_types import ActionInfo
logger = get_logger("replyer")
def init_prompt():
Prompt("你正在qq群里聊天下面是群里在聊的内容", "chat_target_group1")
Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1")
@@ -356,10 +361,14 @@ class DefaultReplyer:
expression_habits_block = ""
expression_habits_title = ""
if style_habits_str.strip():
expression_habits_title = "你可以参考以下的语言习惯,当情景合适就使用,但不要生硬使用,以合理的方式结合到你的回复中:"
expression_habits_title = (
"你可以参考以下的语言习惯,当情景合适就使用,但不要生硬使用,以合理的方式结合到你的回复中:"
)
expression_habits_block += f"{style_habits_str}\n"
if grammar_habits_str.strip():
expression_habits_title = "你可以选择下面的句法进行回复,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式使用:"
expression_habits_title = (
"你可以选择下面的句法进行回复,如果情景合适就使用,不要盲目使用,不要生硬使用,以合理的方式使用:"
)
expression_habits_block += f"{grammar_habits_str}\n"
if style_habits_str.strip() and grammar_habits_str.strip():
@@ -367,7 +376,6 @@ class DefaultReplyer:
expression_habits_block = f"{expression_habits_title}\n{expression_habits_block}"
return expression_habits_block
async def build_memory_block(self, chat_history, target):
@@ -630,13 +638,7 @@ class DefaultReplyer:
sender, target = self._parse_reply_target(reply_to)
target = replace_user_references_in_content(
target,
chat_stream.platform,
is_async=False,
replace_bot_name=True
)
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
# 构建action描述 (如果启用planner)
action_descriptions = ""
@@ -687,16 +689,12 @@ class DefaultReplyer:
self._time_and_run_task(
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
),
self._time_and_run_task(
self.build_relation_info(reply_data), "relation_info"
),
self._time_and_run_task(self.build_relation_info(reply_data), "relation_info"),
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
self._time_and_run_task(
self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "tool_info"
),
self._time_and_run_task(
get_prompt_info(target, threshold=0.38), "prompt_info"
),
self._time_and_run_task(get_prompt_info(target, threshold=0.38), "prompt_info"),
)
# 任务名称中英文映射
@@ -705,7 +703,7 @@ class DefaultReplyer:
"relation_info": "感受关系",
"memory_block": "回忆",
"tool_info": "使用工具",
"prompt_info": "获取知识"
"prompt_info": "获取知识",
}
# 处理结果
@@ -815,10 +813,9 @@ class DefaultReplyer:
--------------------------------
{time_block}
这是你和{sender}的对话,你们正在交流中:
{core_dialogue_prompt}"""
{core_dialogue_prompt}""",
)
# 使用 s4u 风格的模板
template_name = "s4u_style_prompt"
@@ -855,7 +852,7 @@ class DefaultReplyer:
identity_block=identity_block,
sender=sender,
target=target,
chat_info=chat_talking_prompt
chat_info=chat_talking_prompt,
)
# 使用原有的模式
@@ -1081,7 +1078,9 @@ async def get_prompt_info(message: str, threshold: float):
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
# 格式化知识信息
formatted_prompt_info = await global_prompt_manager.format_prompt("knowledge_prompt", prompt_info=related_info)
formatted_prompt_info = await global_prompt_manager.format_prompt(
"knowledge_prompt", prompt_info=related_info
)
return formatted_prompt_info
else:
logger.debug("从LPMM知识库获取知识失败可能是从未导入过知识返回空知识...")

View File

@@ -2,7 +2,7 @@ import time # 导入 time 模块以获取当前时间
import random
import re
from typing import List, Dict, Any, Tuple, Optional, Union, Callable
from typing import List, Dict, Any, Tuple, Optional, Callable
from rich.traceback import install
from src.config.config import global_config
@@ -10,18 +10,17 @@ from src.common.message_repository import find_messages, count_messages
from src.common.database.database_model import ActionRecords
from src.common.database.database_model import Images
from src.person_info.person_info import PersonInfoManager, get_person_info_manager
from src.chat.utils.utils import translate_timestamp_to_human_readable,assign_message_ids
from src.chat.utils.utils import translate_timestamp_to_human_readable, assign_message_ids
install(extra_lines=3)
def replace_user_references_in_content(
def replace_user_references_sync(
content: str,
platform: str,
name_resolver: Union[Callable[[str, str], str], Callable[[str, str], Any]] = None,
is_async: bool = False,
replace_bot_name: bool = True
) -> Union[str, Any]:
name_resolver: Optional[Callable[[str, str], str]] = None,
replace_bot_name: bool = True,
) -> str:
"""
替换内容中的用户引用格式,包括回复<aaa:bbb>和@<aaa:bbb>格式
@@ -29,42 +28,30 @@ def replace_user_references_in_content(
content: 要处理的内容字符串
platform: 平台标识
name_resolver: 名称解析函数,接收(platform, user_id)参数,返回用户名称
如果为None则使用默认的person_info_manager
is_async: 是否为异步模式
如果为None则使用默认的person_info_manager
replace_bot_name: 是否将机器人的user_id替换为"机器人昵称(你)"
Returns:
处理后的内容字符串同步模式或awaitable对象异步模式
str: 处理后的内容字符串
"""
if is_async:
return _replace_user_references_async(content, platform, name_resolver, replace_bot_name)
else:
return _replace_user_references_sync(content, platform, name_resolver, replace_bot_name)
def _replace_user_references_sync(
content: str,
platform: str,
name_resolver: Optional[Callable[[str, str], str]] = None,
replace_bot_name: bool = True
) -> str:
"""同步版本的用户引用替换"""
if name_resolver is None:
person_info_manager = get_person_info_manager()
def default_resolver(platform: str, user_id: str) -> str:
# 检查是否是机器人自己
if replace_bot_name and user_id == global_config.bot.qq_account:
return f"{global_config.bot.nickname}(你)"
person_id = PersonInfoManager.get_person_id(platform, user_id)
return person_info_manager.get_value_sync(person_id, "person_name") or user_id
return person_info_manager.get_value_sync(person_id, "person_name") or user_id # type: ignore
name_resolver = default_resolver
# 处理回复<aaa:bbb>格式
reply_pattern = r"回复<([^:<>]+):([^:<>]+)>"
match = re.search(reply_pattern, content)
if match:
aaa = match.group(1)
bbb = match.group(2)
aaa = match[1]
bbb = match[2]
try:
# 检查是否是机器人自己
if replace_bot_name and bbb == global_config.bot.qq_account:
@@ -83,7 +70,7 @@ def _replace_user_references_sync(
new_content = ""
last_end = 0
for m in at_matches:
new_content += content[last_end:m.start()]
new_content += content[last_end : m.start()]
aaa = m.group(1)
bbb = m.group(2)
try:
@@ -103,21 +90,35 @@ def _replace_user_references_sync(
return content
async def _replace_user_references_async(
async def replace_user_references_async(
content: str,
platform: str,
name_resolver: Optional[Callable[[str, str], Any]] = None,
replace_bot_name: bool = True
replace_bot_name: bool = True,
) -> str:
"""异步版本的用户引用替换"""
"""
替换内容中的用户引用格式,包括回复<aaa:bbb>和@<aaa:bbb>格式
Args:
content: 要处理的内容字符串
platform: 平台标识
name_resolver: 名称解析函数,接收(platform, user_id)参数,返回用户名称
如果为None则使用默认的person_info_manager
replace_bot_name: 是否将机器人的user_id替换为"机器人昵称(你)"
Returns:
str: 处理后的内容字符串
"""
if name_resolver is None:
person_info_manager = get_person_info_manager()
async def default_resolver(platform: str, user_id: str) -> str:
# 检查是否是机器人自己
if replace_bot_name and user_id == global_config.bot.qq_account:
return f"{global_config.bot.nickname}(你)"
person_id = PersonInfoManager.get_person_id(platform, user_id)
return await person_info_manager.get_value(person_id, "person_name") or user_id
return await person_info_manager.get_value(person_id, "person_name") or user_id # type: ignore
name_resolver = default_resolver
# 处理回复<aaa:bbb>格式
@@ -144,7 +145,7 @@ async def _replace_user_references_async(
new_content = ""
last_end = 0
for m in at_matches:
new_content += content[last_end:m.start()]
new_content += content[last_end : m.start()]
aaa = m.group(1)
bbb = m.group(2)
try:
@@ -524,7 +525,7 @@ def _build_readable_messages_internal(
person_name = "某人"
# 使用独立函数处理用户引用格式
content = replace_user_references_in_content(content, platform, is_async=False, replace_bot_name=replace_bot_name)
content = replace_user_references_sync(content, platform, replace_bot_name=replace_bot_name)
target_str = "这是QQ的一个功能用于提及某人但没那么明显"
if target_str in content and random.random() < 0.6:
@@ -778,6 +779,7 @@ async def build_readable_messages_with_list(
return formatted_string, details_list
def build_readable_messages_with_id(
messages: List[Dict[str, Any]],
replace_bot_name: bool = True,
@@ -795,7 +797,7 @@ def build_readable_messages_with_id(
message_id_list = assign_message_ids(messages)
formatted_string = build_readable_messages(
messages = messages,
messages=messages,
replace_bot_name=replace_bot_name,
merge_messages=merge_messages,
timestamp_mode=timestamp_mode,
@@ -806,10 +808,7 @@ def build_readable_messages_with_id(
message_id_list=message_id_list,
)
return formatted_string , message_id_list
return formatted_string, message_id_list
def build_readable_messages(
@@ -894,7 +893,13 @@ def build_readable_messages(
if read_mark <= 0:
# 没有有效的 read_mark直接格式化所有消息
formatted_string, _, pic_id_mapping, _ = _build_readable_messages_internal(
copy_messages, replace_bot_name, merge_messages, timestamp_mode, truncate, show_pic=show_pic, message_id_list=message_id_list
copy_messages,
replace_bot_name,
merge_messages,
timestamp_mode,
truncate,
show_pic=show_pic,
message_id_list=message_id_list,
)
# 生成图片映射信息并添加到最前面
@@ -1017,7 +1022,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str:
for msg in messages:
try:
platform = msg.get("chat_info_platform")
platform: str = msg.get("chat_info_platform") # type: ignore
user_id = msg.get("user_id")
_timestamp = msg.get("time")
content: str = ""
@@ -1047,7 +1052,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str:
except Exception:
return "?"
content = replace_user_references_in_content(content, platform, anon_name_resolver, is_async=False, replace_bot_name=False)
content = replace_user_references_sync(content, platform, anon_name_resolver, replace_bot_name=False)
header = f"{anon_name}"
output_lines.append(header)

View File

@@ -36,7 +36,7 @@ def compare_dicts(new, old, path=None, new_comments=None, old_comments=None, log
continue
if key not in old:
comment = get_key_comment(new, key)
logs.append(f"新增: {'.'.join(path + [str(key)])} 注释: {comment if comment else ''}")
logs.append(f"新增: {'.'.join(path + [str(key)])} 注释: {comment or ''}")
elif isinstance(new[key], (dict, Table)) and isinstance(old.get(key), (dict, Table)):
compare_dicts(new[key], old[key], path + [str(key)], new_comments, old_comments, logs)
# 删减项
@@ -45,7 +45,7 @@ def compare_dicts(new, old, path=None, new_comments=None, old_comments=None, log
continue
if key not in new:
comment = get_key_comment(old, key)
logs.append(f"删减: {'.'.join(path + [str(key)])} 注释: {comment if comment else ''}")
logs.append(f"删减: {'.'.join(path + [str(key)])} 注释: {comment or ''}")
return logs

View File

@@ -29,7 +29,7 @@ from src.common.logger import get_logger
from src.chat.message_receive.chat_stream import get_chat_manager
from src.chat.message_receive.uni_message_sender import HeartFCSender
from src.chat.message_receive.message import MessageSending, MessageRecv
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, replace_user_references_in_content
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, replace_user_references_async
from src.person_info.person_info import get_person_info_manager
from maim_message import Seg, UserInfo
from src.config.config import global_config
@@ -183,7 +183,7 @@ async def _find_reply_message(target_stream, reply_to: str) -> Optional[MessageR
translate_text = message["processed_plain_text"]
# 使用独立函数处理用户引用格式
translate_text = await replace_user_references_in_content(translate_text, platform, is_async=True)
translate_text = await replace_user_references_async(translate_text, platform)
similarity = difflib.SequenceMatcher(None, text, translate_text).ratio()
if similarity >= 0.9:

View File

@@ -9,7 +9,7 @@
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.9.0"
"min_version": "0.9.1"
},
"homepage_url": "https://github.com/MaiM-with-u/maibot",
"repository_url": "https://github.com/MaiM-with-u/maibot",

View File

@@ -429,6 +429,7 @@ class PluginManagementPlugin(BasePlugin):
config_schema: dict = {
"plugin": {
"enable": ConfigField(bool, default=False, description="是否启用插件"),
"config_version": ConfigField(type=str, default="1.0.0", description="配置文件版本"),
"permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表"),
},
}