Merge branch 'Mai-with-u:dev' into dev
This commit is contained in:
@@ -22,6 +22,8 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
# 工作目录
|
||||
WORKDIR /MaiMBot
|
||||
|
||||
ENV MAIBOT_LEGACY_0X_UPGRADE_CONFIRMED=1
|
||||
|
||||
# 复制依赖列表
|
||||
COPY requirements.txt .
|
||||
|
||||
|
||||
@@ -176,6 +176,11 @@ MaiSaka 不仅仅是一个机器人,不仅仅是一个可以帮你完成任务
|
||||
<img alt="contributors" src="https://contrib.rocks/image?repo=MaiM-with-u/MaiBot" />
|
||||
</a>
|
||||
|
||||
### 🤝 开源项目友链
|
||||
<sub><sup>Open Source Friends</sup></sub>
|
||||
|
||||
- **[AstrBot](https://github.com/AstrBotDevs/AstrBot)**: 优秀的LLM Agent项目
|
||||
|
||||
### ❤️ 特别致谢
|
||||
<sub><sup>Special Thanks</sup></sub>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ services:
|
||||
# image: infinitycat/maibot:dev
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- MAIBOT_LEGACY_0X_UPGRADE_CONFIRMED=1 # Docker 无法交互确认旧版升级迁移,默认跳过确认提示
|
||||
# - EULA_AGREE=1b662741904d7155d1ce1c00b3530d0d # 同意EULA
|
||||
# - PRIVACY_AGREE=9943b855e72199d0f5016ea39052f1b6 # 同意EULA
|
||||
ports:
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
- **参数化与动态调整聊天行为**:
|
||||
- 将 `NormalChatInstance` 和 `HeartFlowChatInstance` 中的关键行为参数(例如:回复概率、思考频率、兴趣度阈值、状态转换条件等)提取出来,使其更易于配置。
|
||||
- 允许每个 `SubHeartflow` (即每个聊天场景) 拥有其独立的参数配置,实现"千群千面"。
|
||||
- 开发机制,使得这些参数能够被动态调整:
|
||||
- 基于外部反馈:例如,根据用户评价("话太多"或"太冷淡")调整回复频率。
|
||||
- 基于环境分析:例如,根据群消息的活跃度自动调整参与度。
|
||||
- 基于学习:通过分析历史交互数据,优化特定群聊下的行为模式。
|
||||
- 目标是让 Mai 在不同群聊中展现出更适应环境、更个性化的交互风格。
|
||||
|
||||
- **动态 Prompt 生成与人格塑造**:
|
||||
- 当前 Prompt (提示词) 相对静态。计划实现动态或半结构化的 Prompt 生成。
|
||||
- Prompt 内容可根据以下因素调整:
|
||||
- **人格特质**: 通过参数化配置(如友善度、严谨性等),影响 Prompt 的措辞、语气和思考倾向,塑造更稳定和独特的人格。
|
||||
- **当前情绪**: 将实时情绪状态融入 Prompt,使回复更符合当下心境。
|
||||
- 目标:提升 `HeartFlowChatInstance` (HFC) 回复的多样性、一致性和真实感。
|
||||
- 前置:需要重构 Prompt 构建逻辑,可能引入 `PromptBuilder` 并提供标准接口 (认为是必须步骤)。
|
||||
|
||||
|
||||
- **增强工具调用能力 (Enhanced Tool Usage)**:
|
||||
- 扩展 `HeartFlowChatInstance` (HFC) 可用的工具集。
|
||||
- 考虑引入"元工具"或分层工具机制,允许 HFC 在需要时(如深度思考)访问更强大的工具,例如:
|
||||
- 修改自身或其他 `SubHeartflow` 的聊天参数。
|
||||
- 请求改变 Mai 的全局状态 (`MaiState`)。
|
||||
- 管理日程或执行更复杂的分析任务。
|
||||
- 目标:提升 HFC 的自主决策和行动能力,即使会增加一定的延迟。
|
||||
|
||||
- **标准化人设生成 (Standardized Persona Generation)**:
|
||||
- **目标**: 解决手动配置 `人设` 文件缺乏标准、难以全面描述个性的问题,并生成更丰富、可操作的人格资源。
|
||||
- **方法**: 利用大型语言模型 (LLM) 辅助生成标准化的、结构化的人格**资源包**。
|
||||
- **生成内容**: 不仅生成描述性文本(替代现有 `individual` 配置),还可以同时生成与该人格配套的:
|
||||
- **相关工具 (Tools)**: 该人格倾向于使用的工具或能力。
|
||||
- **初始记忆/知识库 (Memories/Knowledge)**: 定义其背景和知识基础。
|
||||
- **核心行为模式 (Core Behavior Patterns)**: 预置一些典型的行为方式,可作为行为学习的起点。
|
||||
- **实现途径**:
|
||||
- 通过与 LLM 的交互式对话来定义和细化人格及其配套资源。
|
||||
- 让 LLM 分析提供的文本材料(如小说、背景故事)来提取人格特质和相关信息。
|
||||
- **优势**: 替代易出错且标准不一的手动配置,生成更丰富、一致、包含配套资源且易于系统理解和应用的人格包。
|
||||
|
||||
|
||||
- **探索高级记忆检索机制 (GE 系统概念):**
|
||||
- 研究超越简单关键词/近期性检索的记忆模型。
|
||||
- 考虑引入基于事件关联、相对时间线索和绝对时间锚点的检索方式。
|
||||
- 可能涉及设计新的事件表示或记忆结构。
|
||||
|
||||
- **基于人格生成预设知识:**
|
||||
- 开发利用 LLM 和人格配置生成背景知识的功能。
|
||||
- 这些知识应符合角色的行为风格和可能的经历。
|
||||
- 作为一种"冷启动"或丰富角色深度的方式。
|
||||
|
||||
|
||||
1.更nb的工作记忆,直接开一个play_ground,通过llm进行内容检索,这个play_ground可以容纳巨量信息,并且十分通用化,十分好。
|
||||
@@ -1,156 +0,0 @@
|
||||
# LPMM 关键参数调节指南(进阶版)
|
||||
|
||||
> 本文是对 `config/bot_config.toml` 中 `[lpmm_knowledge]` 段的补充说明。
|
||||
> 如果你只想使用默认配置,可以不改这些参数,脚本仍然可以正常工作。
|
||||
>
|
||||
> 重要提醒:无论是修改 `[lpmm_knowledge]` 段的参数,还是通过脚本导入 / 删除 LPMM 知识库数据,主程序都需要重启(或在内部调用一次 `lpmm_start_up()`)后,新的参数和知识才会真正生效到聊天侧。
|
||||
|
||||
所有与 LPMM 相关的参数,都集中在:
|
||||
|
||||
```toml
|
||||
[lpmm_knowledge] # lpmm知识库配置
|
||||
enable = true
|
||||
lpmm_mode = "agent"
|
||||
...
|
||||
```
|
||||
|
||||
下面按功能将常用参数分为三组介绍。
|
||||
|
||||
---
|
||||
|
||||
## 一、检索相关参数(影响答案质量与风格)
|
||||
|
||||
```toml
|
||||
qa_relation_search_top_k = 10 # 关系检索TopK
|
||||
qa_relation_threshold = 0.5 # 关系阈值,相似度高于该值才认为“命中关系”
|
||||
qa_paragraph_search_top_k = 1000 # 段落检索TopK,越小可能影响召回
|
||||
qa_paragraph_node_weight = 0.05 # 段落节点权重,在图检索&PPR中的权重
|
||||
qa_ent_filter_top_k = 10 # 实体过滤TopK
|
||||
qa_ppr_damping = 0.8 # PPR阻尼系数
|
||||
qa_res_top_k = 3 # 最终提供给问答模型的段落数
|
||||
```
|
||||
|
||||
- `qa_relation_search_top_k`
|
||||
控制“最多考虑多少条关系向量候选”。
|
||||
- 数值大:召回更全面,但略慢;
|
||||
- 数值小:更快,可能遗漏部分隐含关系。
|
||||
|
||||
- `qa_relation_threshold`
|
||||
关系相似度的阈值:
|
||||
- 数值高:只信任非常相关的关系,系统更可能退化为纯段落向量检索;
|
||||
- 数值低:图结构影响更大,适合实体关系较丰富的场景。
|
||||
|
||||
- `qa_paragraph_search_top_k`
|
||||
控制“最多考虑多少段落候选”。
|
||||
- 太小:可能召回不全,导致答案缺失;
|
||||
- 太大:略微增加计算量,一般 1000 为安全默认。
|
||||
|
||||
- `qa_paragraph_node_weight`
|
||||
文段节点在图检索中的权重:
|
||||
- 数值大:更依赖段落向量相似度(传统向量检索);
|
||||
- 数值小:更依赖图结构和实体网络。
|
||||
|
||||
- `qa_ppr_damping`
|
||||
Personalized PageRank 的阻尼系数:
|
||||
- 通常保持在 0.8 左右即可;
|
||||
- 越接近 1:偏向长路径探索,结果更发散;
|
||||
- 略低:更集中在与问题直接相关的节点附近。
|
||||
|
||||
- `qa_res_top_k`
|
||||
LPMM 最终会把相关度最高的前 `qa_res_top_k` 条段落组合成“知识上下文”给问答模型。
|
||||
- 太多:增加模型负担、阅读更多文字;
|
||||
- 太少:信息不够充分,一般 3–5 比较平衡。
|
||||
|
||||
> 调参建议:
|
||||
> - 优先在 `qa_relation_threshold`、`qa_paragraph_node_weight` 上做小幅调整;
|
||||
> - 每次调整后,用 `scripts/test_lpmm_retrieval.py` 跑一遍固定问题,感受回答变化。
|
||||
|
||||
---
|
||||
|
||||
## 二、性能与硬件相关参数
|
||||
|
||||
```toml
|
||||
embedding_dimension = 1024 # 嵌入向量维度,应与模型输出维度一致
|
||||
max_embedding_workers = 12 # 嵌入/抽取并发线程数
|
||||
embedding_chunk_size = 16 # 每批嵌入的条数
|
||||
info_extraction_workers = 3 # 实体抽取同时执行线程数
|
||||
enable_ppr = true # 是否启用PPR,低配机器可关闭
|
||||
```
|
||||
|
||||
- `embedding_dimension`
|
||||
必须与所选嵌入模型的输出维度一致(比如 768、1024 等)。**不要随意修改,除非你知道你在做什么!!!**
|
||||
|
||||
- `max_embedding_workers`
|
||||
决定导入/抽取阶段的并行线程数:
|
||||
- 机器配置好:可以适当调大,加快导入速度;
|
||||
- 机器配置弱:建议调低(如 2 或 4),避免 CPU 长时间 100%。
|
||||
|
||||
- `embedding_chunk_size`
|
||||
每批发送给嵌入 API 的段落数量:
|
||||
- 数值大:请求次数少,但单次请求更“重”;
|
||||
- 数值小:请求次数多,但对网络和 API 的单次压力小。
|
||||
|
||||
- `info_extraction_workers`
|
||||
`scripts/info_extraction.py` 中实体抽取的并行线程数:
|
||||
- 使用 Pro/贵价模型时建议不要太大,避免并行费用过高;
|
||||
- 一般 2–4 就能取得较好平衡。
|
||||
|
||||
- `enable_ppr`
|
||||
是否启用个性化 PageRank(PPR)图检索:
|
||||
- `true`:检索会结合向量+知识图,效果更好,但略慢;
|
||||
- `false`:只用向量检索,牺牲一定效果,性能更稳定。
|
||||
|
||||
|
||||
> 调参建议:
|
||||
> - 若导入/检索阶段机器明显“顶不住”(>=1MB的大文本,且分配配置<4C),优先调低:
|
||||
> - `max_embedding_workers`
|
||||
> - `embedding_chunk_size`
|
||||
> - `info_extraction_workers`
|
||||
> - 或暂时将 `enable_ppr = false` (除非真的出现问题,否则不建议禁用此项,大幅影响检索效果)
|
||||
> - 调整后重新执行导入或检索,观察日志与系统资源占用。
|
||||
|
||||
> 小提示:每次大改参数或批量删除知识后,建议用
|
||||
> - `scripts/test_lpmm_retrieval.py` 看回答风格是否如预期;
|
||||
> - 如需确认当前磁盘数据能否正常初始化,可执行 `scripts/refresh_lpmm_knowledge.py` 做一次快速自检。
|
||||
|
||||
---
|
||||
|
||||
## 三、开启/关闭 LPMM 与模式说明
|
||||
|
||||
```toml
|
||||
enable = true # 是否开启lpmm知识库
|
||||
lpmm_mode = "agent" # 可选 classic / agent
|
||||
```
|
||||
|
||||
- `enable`
|
||||
- `true`:LPMM 知识库启用,检索和问答会使用知识库;
|
||||
- `false`:LPMM 完全关闭,脚本仍可导入/删除数据,但对聊天问答不生效。
|
||||
|
||||
- `lpmm_mode`
|
||||
- `classic`:传统模式,仅使用 LPMM 知识库本身;
|
||||
- `agent`:与新的记忆系统联动,用于更复杂的记忆+知识混合场景。
|
||||
|
||||
> 修改 `enable` 或 `lpmm_mode` 后,需要重启主程序,让配置生效。
|
||||
|
||||
---
|
||||
|
||||
## 四、推荐的调参流程
|
||||
|
||||
1. **保持默认配置,先跑一轮完整流程**
|
||||
- 导入 → `inspect_lpmm_global.py` → `test_lpmm_retrieval.py`;
|
||||
- 记录当前“答案风格”和“响应速度”。
|
||||
|
||||
2. **每次只调整一到两个参数**
|
||||
- 例如先调 `qa_relation_threshold`、`qa_paragraph_node_weight`;
|
||||
- 或在性能不佳时调整 `max_embedding_workers`、`enable_ppr`。
|
||||
|
||||
3. **调整后重复同一组测试问题**
|
||||
- 使用 `scripts/test_lpmm_retrieval.py`;
|
||||
- 对比不同配置下的答案,选择更符合需求的组合。
|
||||
|
||||
4. **出现“怎么调都不对”时**
|
||||
- 将 `[lpmm_knowledge]` 段恢复为仓库中的默认配置;
|
||||
- 重启主程序,即可回到“出厂设置”。
|
||||
|
||||
通过本指南中的参数调节,你可以在“检索质量”“响应速度”“系统资源占用”之间找到适合自己麦麦和机器的平衡点!
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
## LPMM 知识库流水线使用指南(命令行版)
|
||||
|
||||
本文档介绍如何使用 `scripts/lpmm_manager.py` 及相关子脚本,完成 **导入 / 删除 / 自检 / 刷新 / 回归测试** 等常见流水线操作,并说明各参数在交互式与非交互(脚本化)场景下的用法。
|
||||
|
||||
所有命令均假设在项目根目录 `MaiBot/` 下执行:
|
||||
|
||||
```bash
|
||||
cd MaiBot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 管理脚本总览:`scripts/lpmm_manager.py`
|
||||
|
||||
### 1.1 基本用法
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py [--interactive] [-a ACTION] [--non-interactive] [-- ...子脚本参数...]
|
||||
```
|
||||
|
||||
- `--interactive` / `-i`:进入交互式菜单模式(推荐人工运维时使用)。
|
||||
- `--action` / `-a`:直接执行指定操作(非交互入口),可选值:
|
||||
- `prepare_raw`:预处理 `data/lpmm_raw_data/*.txt`。
|
||||
- `info_extract`:信息抽取,生成 OpenIE JSON 批次。
|
||||
- `import_openie`:导入 OpenIE 批次到向量库与知识图。
|
||||
- `delete`:删除/回滚知识(封装 `delete_lpmm_items.py`)。
|
||||
- `batch_inspect`:检查指定 OpenIE 批次的存在情况。
|
||||
- `global_inspect`:全库状态统计。
|
||||
- `refresh`:刷新 LPMM 磁盘数据到内存。
|
||||
- `test`:检索效果回归测试。
|
||||
- `full_import`:一键执行「预处理原始语料 → 信息抽取 → 导入 → 刷新」。
|
||||
- `--non-interactive`:
|
||||
- 启用 **非交互模式**:`lpmm_manager` 自身不会再调用 `input()` 询问确认;
|
||||
- 同时自动向子脚本透传 `--non-interactive`(若子脚本支持),用于在 CI / 定时任务中实现无人值守。
|
||||
- `--` 之后的内容会原样传递给对应子脚本的 `main()`,用于设置更细粒度参数。
|
||||
|
||||
> 注意:`--interactive` 与 `--non-interactive` 互斥,不能同时使用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 典型流水线一:全量导入(从原始 txt 到可用 LPMM)
|
||||
|
||||
### 2.1 前置条件
|
||||
|
||||
- 将待导入的原始文本放入:
|
||||
|
||||
```text
|
||||
data/lpmm_raw_data/*.txt
|
||||
```
|
||||
|
||||
- 文本按「空行分段」,每个段落为一条候选知识。
|
||||
|
||||
### 2.2 一键全流程(交互式)
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py --interactive
|
||||
```
|
||||
|
||||
菜单中依次:
|
||||
|
||||
1. 选择 `9. full_import`(预处理 → 信息抽取 → 导入 → 刷新)。
|
||||
2. 按提示确认可能的费用与时间消耗。
|
||||
3. 等待脚本执行完成。
|
||||
|
||||
### 2.3 一键全流程(非交互 / CI 友好)
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a full_import --non-interactive
|
||||
```
|
||||
|
||||
执行顺序:
|
||||
|
||||
1. `prepare_raw`:调用 `raw_data_preprocessor.load_raw_data()`,统计段落与去重哈希数。
|
||||
2. `info_extract`:调用 `info_extraction.main(--non-interactive)`,从 `data/lpmm_raw_data` 读取段落,生成 OpenIE JSON 并写入 `data/openie/`。
|
||||
3. `import_openie`:调用 `import_openie.main(--non-interactive)`,导入 OpenIE 批次到嵌入库与 KG。
|
||||
4. `refresh`:调用 `refresh_lpmm_knowledge.main()`,刷新 LPMM 知识库到内存。
|
||||
|
||||
在 `--non-interactive` 模式下:
|
||||
|
||||
- 若 `data/lpmm_raw_data` 中没有 `.txt` 文件,或 `data/openie` 中没有 `.json` 文件,将直接报错退出,并在日志中说明缺少的目录/文件。
|
||||
- 若 OpenIE 批次中存在非法文段,导入脚本会 **直接报错退出**,不会卡在交互确认上。
|
||||
|
||||
---
|
||||
|
||||
## 3. 典型流水线二:分步导入
|
||||
|
||||
若需要逐步调试或只执行部分步骤,可以分开调用:
|
||||
|
||||
### 3.1 预处理原始语料:`prepare_raw`
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a prepare_raw
|
||||
```
|
||||
|
||||
行为:
|
||||
- 使用 `raw_data_preprocessor.load_raw_data()` 读取 `data/lpmm_raw_data/*.txt`;
|
||||
- 输出段落总数与去重后的哈希数,供人工检查原始数据质量。
|
||||
|
||||
### 3.2 信息抽取:`info_extract`
|
||||
|
||||
#### 交互式(带费用提示)
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a info_extract
|
||||
```
|
||||
|
||||
脚本会:
|
||||
- 打印预计费用/时间提示;
|
||||
- 询问 `确认继续执行?(y/n)`;
|
||||
- 然后开始从 `data/lpmm_raw_data` 中读取段落,调用 LLM 提取实体与三元组,并生成 OpenIE JSON。
|
||||
|
||||
#### 非交互式(无人工确认)
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a info_extract --non-interactive
|
||||
```
|
||||
|
||||
行为差异:
|
||||
- 跳过`确认继续执行`的交互提示,直接开始抽取;
|
||||
- 若 `data/lpmm_raw_data` 下没有 `.txt` 文件,会打印告警并以错误方式退出。
|
||||
|
||||
### 3.3 导入 OpenIE 批次:`import_openie`
|
||||
|
||||
#### 交互式
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a import_openie
|
||||
```
|
||||
|
||||
脚本会:
|
||||
- 提示导入开销与资源占用情况;
|
||||
- 询问是否继续;
|
||||
- 调用 `OpenIE.load()` 加载批次,再将其导入嵌入库与 KG。
|
||||
|
||||
#### 非交互式
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a import_openie --non-interactive
|
||||
```
|
||||
|
||||
- 跳过导入开销确认;
|
||||
- 若数据存在非法文段:
|
||||
- 在交互模式下会询问是否删除这些非法文段并继续;
|
||||
- 在非交互模式下,会直接 `logger.error` 并 `sys.exit(1)`,防止导入不完整数据。
|
||||
|
||||
> 提示:当前 `OpenIE.load()` 仍可能在内部要求你选择具体批次文件,若需完全无交互的导入,可后续扩展为显式指定文件路径。
|
||||
|
||||
### 3.4 刷新 LPMM 知识库:`refresh`
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a refresh
|
||||
# 或
|
||||
python scripts/lpmm_manager.py -a refresh --non-interactive
|
||||
```
|
||||
|
||||
两者行为相同:
|
||||
- 调用 `refresh_lpmm_knowledge.main()`,内部执行 `lpmm_start_up()`;
|
||||
- 日志中输出当前向量与 KG 规模,验证导入是否成功。
|
||||
|
||||
---
|
||||
|
||||
## 4. 典型流水线三:删除 / 回滚
|
||||
|
||||
删除操作通过 `lpmm_manager.py -a delete` 封装 `scripts/delete_lpmm_items.py`。
|
||||
|
||||
### 4.1 交互式删除(推荐人工操作)
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py --interactive
|
||||
```
|
||||
|
||||
菜单中选择:
|
||||
|
||||
1. `4. delete - 删除/回滚知识`
|
||||
2. 再选择删除方式:
|
||||
- 按哈希文件(`--hash-file`)
|
||||
- 按 OpenIE 批次(`--openie-file`)
|
||||
- 按原始语料 + 段落索引(`--raw-file + --raw-index`)
|
||||
- 按关键字搜索现有段落(`--search-text`)
|
||||
3. 管理脚本会根据你的选择自动拼好常用参数(是否删除实体/关系、是否删除孤立实体、是否 dry-run、是否自动确认等),最后调用 `delete_lpmm_items.py` 执行。
|
||||
|
||||
### 4.2 非交互删除(CI / 脚本场景)
|
||||
|
||||
#### 示例:按哈希文件删除(带完整保护参数)
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a delete --non-interactive -- \
|
||||
--hash-file data/lpmm_delete_hashes.txt \
|
||||
--delete-entities \
|
||||
--delete-relations \
|
||||
--remove-orphan-entities \
|
||||
--max-delete-nodes 2000 \
|
||||
--yes
|
||||
```
|
||||
|
||||
- `--non-interactive`(manager):禁止任何 `input()` 询问;
|
||||
- 子脚本 `delete_lpmm_items.py` 中:
|
||||
- `--hash-file`:指定待删段落哈希列表;
|
||||
- `--delete-entities` / `--delete-relations` / `--remove-orphan-entities`:同步清理实体与关系;
|
||||
- `--max-delete-nodes`:单次删除节点数上限,避免误删过大规模;
|
||||
- `--yes`:跳过终极确认,适合已验证的自动流水线。
|
||||
|
||||
#### 按 OpenIE 批次删除(常用于批次回滚)
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a delete --non-interactive -- \
|
||||
--openie-file data/openie/2025-01-01-12-00-openie.json \
|
||||
--delete-entities \
|
||||
--delete-relations \
|
||||
--remove-orphan-entities \
|
||||
--yes
|
||||
```
|
||||
|
||||
### 4.3 非交互模式下的安全限制
|
||||
|
||||
在 `delete_lpmm_items.py` 中:
|
||||
|
||||
- 若使用 `--search-text`,需要用户通过输入序号选择要删条目;
|
||||
- 在 `--non-interactive` 模式下,这一步会直接报错退出,提示改用 `--hash-file / --openie-file / --raw-file` 等纯参数方式。
|
||||
- 若未指定 `--yes`:
|
||||
- 非交互模式下会报错退出,提示「非交互模式且未指定 --yes,出于安全考虑删除操作已被拒绝」。
|
||||
|
||||
---
|
||||
|
||||
## 5. 典型流水线四:自检与状态检查
|
||||
|
||||
### 5.1 检查指定 OpenIE 批次状态:`batch_inspect`
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a batch_inspect -- --openie-file data/openie/xx.json
|
||||
```
|
||||
|
||||
输出该批次在当前库中的:
|
||||
- 段落向量数量 / KG 段落节点数量;
|
||||
- 实体向量数量 / KG 实体节点数量;
|
||||
- 关系向量数量;
|
||||
- 少量仍存在的样例内容。
|
||||
|
||||
常用于:
|
||||
- 导入后确认是否完全成功;
|
||||
- 删除后确认是否完全回滚。
|
||||
|
||||
### 5.2 查看整库状态:`global_inspect`
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a global_inspect
|
||||
```
|
||||
|
||||
输出:
|
||||
- 段落 / 实体 / 关系向量条数;
|
||||
- KG 节点/边总数,段落节点数、实体节点数;
|
||||
- 实体计数表 `ent_appear_cnt` 的条目数;
|
||||
- 少量剩余段落/实体样例,便于快速 sanity check。
|
||||
|
||||
---
|
||||
|
||||
## 6. 典型流水线五:检索效果回归测试
|
||||
|
||||
### 6.1 使用默认测试用例
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a test
|
||||
```
|
||||
|
||||
- 调用 `test_lpmm_retrieval.py` 内置的 `DEFAULT_TEST_CASES`;
|
||||
- 对每条用例输出:
|
||||
- 原始结果;
|
||||
- 状态(`PASS` / `WARN` / `NO_HIT` / `ERROR`);
|
||||
- 期望关键字与命中关键字列表。
|
||||
|
||||
### 6.2 自定义测试问题与期望关键字
|
||||
|
||||
```bash
|
||||
python scripts/lpmm_manager.py -a test -- --query "LPMM 是什么?" \
|
||||
--expect-keyword 哈希列表 \
|
||||
--expect-keyword 删除脚本
|
||||
```
|
||||
|
||||
也可以直接调用子脚本:
|
||||
|
||||
```bash
|
||||
python scripts/test_lpmm_retrieval.py \
|
||||
--query "LPMM 是什么?" \
|
||||
--expect-keyword 哈希列表 \
|
||||
--expect-keyword 删除脚本
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 推荐组合示例
|
||||
|
||||
### 7.1 导入 + 刷新 + 简单回归
|
||||
|
||||
```bash
|
||||
# 1. 执行全量导入(支持非交互)
|
||||
python scripts/lpmm_manager.py -a full_import --non-interactive
|
||||
|
||||
# 2. 使用内置用例做一次检索回归
|
||||
python scripts/lpmm_manager.py -a test
|
||||
```
|
||||
|
||||
### 7.2 批次回滚 + 自检
|
||||
|
||||
```bash
|
||||
TARGET_BATCH=data/openie/2025-01-01-12-00-openie.json
|
||||
|
||||
# 1. 按批次删除(非交互)
|
||||
python scripts/lpmm_manager.py -a delete --non-interactive -- \
|
||||
--openie-file "$TARGET_BATCH" \
|
||||
--delete-entities \
|
||||
--delete-relations \
|
||||
--remove-orphan-entities \
|
||||
--yes
|
||||
|
||||
# 2. 检查该批次是否彻底删除
|
||||
python scripts/lpmm_manager.py -a batch_inspect -- --openie-file "$TARGET_BATCH"
|
||||
|
||||
# 3. 查看全库状态
|
||||
python scripts/lpmm_manager.py -a global_inspect
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
如需扩展更多流水线(例如「导入特定批次后自动跑自定义测试用例」),可以在 `scripts/lpmm_manager.py` 中新增对应的 `ACTION_INFO` 条目和 `run_action` 分支,或直接在 CI / shell 脚本中串联上述命令。该管理脚本已支持参数化与非交互调用,适合作为二次封装的基础入口。
|
||||
|
||||
|
||||
@@ -1,411 +0,0 @@
|
||||
# LPMM 知识库脚本使用指南(零基础用户版)
|
||||
|
||||
本指南面向不熟悉命令行和代码的 C 端用户,帮助你完成:
|
||||
|
||||
- LPMM 知识库的初始部署(从本地 txt 到可检索知识库)
|
||||
- 安全删除知识(按批次、按原文、按哈希、按关键字)
|
||||
- 导入 / 删除后的自检与检索效果验证
|
||||
|
||||
> 说明:本文默认你已经完成 MaiBot 的基础安装,并能在项目根目录打开命令行终端。
|
||||
> 重要提醒:每次使用导入 / 删除相关脚本(如 `import_openie.py`、`delete_lpmm_items.py`)修改 LPMM 知识库后,聊天机器人 / WebUI 端要想看到最新知识,需要重启主程序,或在主程序内部显式调用一次 `lpmm_start_up()` 重新初始化 LPMM
|
||||
|
||||
---
|
||||
。
|
||||
|
||||
|
||||
## 一、需要用到的脚本一览
|
||||
|
||||
在项目根目录(`MaiBot-dev`)下,这些脚本是 LPMM 相关的“工具箱”:
|
||||
|
||||
- 导入相关:
|
||||
- `scripts/raw_data_preprocessor.py`
|
||||
从 `data/lpmm_raw_data` 目录读取 `.txt` 文件,按空行拆分为一个个段落,并做去重。
|
||||
- `scripts/info_extraction.py`
|
||||
调用大模型,从每个段落里抽取实体和三元组,生成中间的 OpenIE JSON 文件。
|
||||
- `scripts/import_openie.py`
|
||||
把 `data/openie` 目录中的 OpenIE JSON 文件导入到 LPMM 知识库(向量库 + 知识图)。
|
||||
- 删除相关:
|
||||
- `scripts/delete_lpmm_items.py`
|
||||
LPMM 知识库删除入口,支持按批次、按原始文本段落、按哈希列表、按关键字模糊搜索删除。
|
||||
- 自检相关:
|
||||
- `scripts/inspect_lpmm_global.py`
|
||||
查看整个知识库的当前状态:段落/实体/关系条数、知识图节点/边数量、示例内容等。
|
||||
- `scripts/inspect_lpmm_batch.py`
|
||||
针对某个 OpenIE JSON 批次,检查它在向量库和知识图中的“残留情况”(导入与删除前后对比)。
|
||||
- `scripts/test_lpmm_retrieval.py`
|
||||
使用几条预设问题测试 LPMM 检索能力,帮助你判断知识库是否正常工作。
|
||||
- `scripts/refresh_lpmm_knowledge.py`
|
||||
手动重新加载 `data/embedding` 和 `data/rag` 到内存,用来确认当前磁盘上的 LPMM 知识库能正常初始化。
|
||||
|
||||
> 注意:所有命令示例都假设你已经在虚拟环境中,命令行前缀类似 `(.venv)`,并且当前目录是项目根目录。
|
||||
|
||||
---
|
||||
|
||||
## 二、LPMM 知识库的初始部署
|
||||
|
||||
### 2.1 准备原始 txt 文本
|
||||
|
||||
1. 把要导入的知识文档放到:
|
||||
|
||||
```text
|
||||
data/lpmm_raw_data
|
||||
```
|
||||
|
||||
2. 文件要求:
|
||||
|
||||
- 必须是 `.txt` 文件,建议使用 UTF-8 编码;
|
||||
- 用**空行**分隔段落:一段话后空一行,即视为一条独立知识。
|
||||
|
||||
示例文件:
|
||||
|
||||
- `data/lpmm_raw_data/lpmm_large_sample.txt`:仓库内已经提供了一份大样本测试文本,可以直接用来练习。
|
||||
|
||||
### 2.2 第一步:预处理原始文本(拆段 + 去重)
|
||||
|
||||
在项目根目录执行:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/raw_data_preprocessor.py
|
||||
```
|
||||
|
||||
成功时通常会看到日志类似:
|
||||
|
||||
- 正在处理文件: `lpmm_large_sample.txt`
|
||||
- 共读取到 XX 条数据
|
||||
|
||||
这一步不会调用大模型,仅做拆段和去重。
|
||||
|
||||
### 2.3 第二步:进行信息抽取(生成 OpenIE JSON)
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/info_extraction.py
|
||||
```
|
||||
|
||||
你会看到一个“重要操作确认”提示,说明:
|
||||
|
||||
- 信息抽取会调用大模型,消耗 API 费用和时间;
|
||||
- 如果确认无误,输入 `y` 回车继续。
|
||||
|
||||
提取过程中可能出现:
|
||||
|
||||
- 类似“模型 ... 网络错误(可重试)”这样的日志;
|
||||
这表示脚本在遇到网络问题时自动重试,一般无需手动干预。
|
||||
|
||||
运行结束后,会有类似提示:
|
||||
|
||||
```text
|
||||
信息提取结果已保存到: data/openie/11-27-10-06-openie.json
|
||||
```
|
||||
|
||||
- 请记住这个文件名,比如:`11-27-10-06-openie.json`
|
||||
接下来我们会用 `<OPENIE>` 来代指这类文件。
|
||||
|
||||
### 2.4 第三步:导入 OpenIE 数据到 LPMM 知识库
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/import_openie.py
|
||||
```
|
||||
|
||||
这个脚本会:
|
||||
|
||||
- 从 `data/openie` 目录读取所有 `*.json` 文件,并合并导入;
|
||||
- 将新段落的嵌入向量写入 `data/embedding`;
|
||||
- 将三元组构建为知识图写入 `data/rag`。
|
||||
|
||||
> 提示:如果你希望“只导入某几批数据”,可以暂时把不需要的 JSON 文件移出 `data/openie`,导入结束后再移回。
|
||||
|
||||
### 2.5 第四步:全局自检(确认导入成功)
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/inspect_lpmm_global.py
|
||||
```
|
||||
|
||||
你会看到类似输出:
|
||||
|
||||
- 段落向量条数: `52`
|
||||
- 实体向量条数: `260`
|
||||
- 关系向量条数: `299`
|
||||
- KG 节点总数 / 边总数 / 段落节点数 / 实体节点数
|
||||
- 若干条示例段落与实体内容预览
|
||||
|
||||
只要这些数字大于 0,就表示 LPMM 知识库已经有可用的数据了。
|
||||
|
||||
### 2.6 第五步:用脚本测试 LPMM 检索效果(可选但推荐)
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/test_lpmm_retrieval.py
|
||||
```
|
||||
|
||||
脚本会:
|
||||
|
||||
- 自动初始化 LPMM(加载向量库与知识图);
|
||||
- 用几条预设问题查询 LPMM;
|
||||
- 打印原始检索结果和关键词命中情况。
|
||||
|
||||
你可以通过观察“RAW RESULT”里的内容,粗略判断:
|
||||
|
||||
- 能否命中与问题高度相关的知识;
|
||||
- 删除或导入新知识后,回答内容是否发生变化。
|
||||
|
||||
---
|
||||
|
||||
## 三、安全删除知识的几种方式
|
||||
|
||||
> 强烈建议:删除前先备份以下目录,以便“回档”:
|
||||
>
|
||||
> - `data/embedding`(向量库)
|
||||
> - `data/rag`(知识图)
|
||||
|
||||
所有删除操作使用同一个脚本:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/delete_lpmm_items.py [参数...]
|
||||
```
|
||||
|
||||
脚本特点:
|
||||
|
||||
- 删除前会打印“待删除段落数量 / 实体数量 / 关系数量 / 预计删除节点数”等摘要;
|
||||
- 需要你输入大写 `YES` 确认才会真正执行;
|
||||
- 支持多种删除策略,可灵活组合。
|
||||
|
||||
### 3.1 按批次删除(推荐:整批回滚)
|
||||
|
||||
适用场景:某次导入的整批知识有问题,希望整体回滚。
|
||||
|
||||
1. 删除前,先检查该批次状态:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/inspect_lpmm_batch.py ^
|
||||
--openie-file data/openie/<OPENIE>.json
|
||||
```
|
||||
|
||||
你会看到该批次:
|
||||
|
||||
- 段落:总计多少条、向量库剩余多少、KG 中剩余多少;
|
||||
- 实体、关系的类似统计;
|
||||
- 少量示例段落/实体内容预览。
|
||||
|
||||
2. 确认无误后,按批次删除:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/delete_lpmm_items.py ^
|
||||
--openie-file data/openie/<OPENIE>.json ^
|
||||
--delete-entities --delete-relations --remove-orphan-entities
|
||||
```
|
||||
|
||||
参数含义:
|
||||
|
||||
- `--delete-entities`:删除该批次涉及的实体向量;
|
||||
- `--delete-relations`:删除该批次涉及的关系向量;
|
||||
- `--remove-orphan-entities`:顺带清理删除后不再参与任何边的“孤立实体”节点。
|
||||
|
||||
3. 删除后再检查:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/inspect_lpmm_batch.py ^
|
||||
--openie-file data/openie/<OPENIE>.json
|
||||
|
||||
.\.venv\Scripts\python.exe scripts/inspect_lpmm_global.py
|
||||
```
|
||||
|
||||
若批次检查显示“向量库剩余 0 / KG 中剩余 0”,则说明该批次已被彻底删除。
|
||||
|
||||
### 3.2 按原始文本段落删除(精确定位某一段)
|
||||
|
||||
适用场景:某个原始 txt 的特定段落写错了,只想删这段对应的知识。
|
||||
|
||||
命令示例:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/delete_lpmm_items.py ^
|
||||
--raw-file data/lpmm_raw_data/lpmm_large_sample.txt ^
|
||||
--raw-index 2
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `--raw-index` 从 1 开始计数,可用逗号多选,例如:`1,3,5`;
|
||||
- 脚本会展示该段落的内容预览和哈希值,再请求你确认。
|
||||
|
||||
### 3.3 按哈希列表删除(进阶用法)
|
||||
|
||||
适用场景:你有一份“需要删除的段落哈希列表”(比如从其他系统导出)。
|
||||
|
||||
示例哈希列表文件:
|
||||
|
||||
- `data/openie/lpmm_delete_test_hashes.txt`
|
||||
|
||||
命令:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/delete_lpmm_items.py ^
|
||||
--hash-file data/openie/lpmm_delete_test_hashes.txt
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 文件中每行一条,可以是 `paragraph-xxxx` 或纯哈希,脚本会自动识别;
|
||||
- 适合“精确控制删除哪些段落”,但准备哈希列表需要一定技术基础。
|
||||
|
||||
### 3.4 按关键字模糊搜索删除(对非技术用户最友好)
|
||||
|
||||
适用场景:只知道某段话里包含某个关键词,不知道它在哪个 txt 或批次里。
|
||||
|
||||
示例 1:删除与“近义词扩展”相关的段落
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/delete_lpmm_items.py --search-text "近义词扩展" --search-limit 5
|
||||
```
|
||||
|
||||
示例 2:删除与“LPMM”强相关的一些段落
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/delete_lpmm_items.py --search-text "LPMM" --search-limit 20
|
||||
|
||||
```
|
||||
|
||||
执行过程:
|
||||
|
||||
1. 脚本在当前段落库中查找包含该关键字的段落;
|
||||
2. 列出前 N 条候选(`--search-limit` 决定数量);
|
||||
3. 提示你输入要删除的序号列表,例如:`1,2,5`;
|
||||
4. 再次提示你输入 `YES` 确认,才会真正执行删除。
|
||||
|
||||
> 建议:
|
||||
>
|
||||
> - 第一次使用时可以先加 `--dry-run` 看看效果:
|
||||
> ```bash
|
||||
> .\.venv\Scripts\python.exe scripts/delete_lpmm_items.py ^
|
||||
> --search-text "LPMM" ^
|
||||
> --search-limit 20 ^
|
||||
> --dry-run
|
||||
> ```
|
||||
> - 确认候选列表确实是你要删的内容后,再去掉 `--dry-run` 正式执行。
|
||||
|
||||
---
|
||||
|
||||
## 四、自检:如何确认导入 / 删除是否“生效”
|
||||
|
||||
### 4.1 全局状态检查
|
||||
|
||||
每次导入或删除之后,建议跑一次:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/inspect_lpmm_global.py
|
||||
```
|
||||
|
||||
你可以在这里看到:
|
||||
|
||||
- 段落向量条数、实体向量条数、关系向量条数;
|
||||
- 知识图的节点总数、边总数、段落节点和实体节点数量;
|
||||
- 若干条“剩余段落示例”和“剩余实体示例”。
|
||||
|
||||
观察方式:
|
||||
|
||||
- 导入后:数字应该明显上升(说明新增数据生效);
|
||||
- 删除后:数字应该明显下降(说明删除操作生效)。
|
||||
|
||||
### 4.2 某个批次的局部状态
|
||||
|
||||
如果你想确认“某一个 OpenIE 文件对应的那一批知识”是否存在,可以使用:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/inspect_lpmm_batch.py --openie-file data/openie/<OPENIE>.json
|
||||
```
|
||||
|
||||
输出中会包含:
|
||||
|
||||
- 该批次的段落 / 实体 / 关系的总数;
|
||||
- 在向量库中还剩多少条,在 KG 中还剩多少条;
|
||||
- 若干条仍存在的段落/实体示例。
|
||||
|
||||
典型用法:
|
||||
|
||||
- 导入后立刻检查一次:确认这一批已经“写入”;
|
||||
- 删除后再检查一次:确认这一批是否已经“清空”。
|
||||
|
||||
### 4.3 检索效果回归测试
|
||||
|
||||
每次做完导入或删除,你都可以用这条命令快速验证检索效果:
|
||||
|
||||
```bash
|
||||
.\.venv\Scripts\python.exe scripts/test_lpmm_retrieval.py
|
||||
```
|
||||
|
||||
它会:
|
||||
|
||||
- 初始化 LPMM(加载当前向量库和知识图);
|
||||
- 用几条预设问题(包括与 LPMM 和配置相关的问题)进行检索;
|
||||
- 打印检索结果以及命中关键词情况。
|
||||
|
||||
通过对比不同时间点的输出,你可以判断:
|
||||
|
||||
- 某些知识是否已经被成功删除(不再出现在回答中);
|
||||
|
||||
- 新增的知识是否已经能被检索到。
|
||||
|
||||
### 4.4 进阶:一键刷新(可选)
|
||||
|
||||
- 想简单确认“现在这份 data/embedding + data/rag 是否健康”?执行:
|
||||
|
||||
`.\.venv\Scripts\python.exe scripts/refresh_lpmm_knowledge.py `
|
||||
|
||||
它会尝试初始化 LPMM,并打印当前段落/实体/关系条数和图大小。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 五、常见提示与注意事项
|
||||
|
||||
1. **看到“网络错误(可重试)”需要担心吗?**
|
||||
|
||||
- 不需要。
|
||||
- 这些日志说明脚本在自动处理网络抖动,多数情况下会在重试后成功返回结果。
|
||||
- 只要脚本最后没有报“重试耗尽并退出”,一般导入/提取结果是有效的。
|
||||
|
||||
2. **删除操作会不会“一删全没”?**
|
||||
|
||||
- 不会直接“一删全没”:
|
||||
- 每次删除会打印摘要信息;
|
||||
- 必须输入 `YES` 才会真正执行;
|
||||
- 大批次时还有 `--max-delete-nodes` 保护,超过阈值会警告。
|
||||
- 但仍然建议:
|
||||
- 在大规模删除前备份 `data/embedding` 和 `data/rag`;
|
||||
- 先通过 `--dry-run` 看看待删列表。
|
||||
|
||||
3. **可以多次导入吗?需要先清空吗?**
|
||||
|
||||
- 可以多次导入,系统会根据段落内容的哈希做去重;
|
||||
- 不需要每次都清空,只要你希望老数据仍然保留即可;
|
||||
- 如果你确实想“重来一遍”,可以:
|
||||
- 先备份,然后删除 `data/embedding` 和 `data/rag`;
|
||||
- 再重新跑导入流程。
|
||||
|
||||
4. **LPMM 开关在哪里?**
|
||||
|
||||
- 配置文件:`config/bot_config.toml`;
|
||||
- 小节:`[lpmm_knowledge]`;
|
||||
- 其中有 `enable = true/false` 开关:
|
||||
- 为 `true`:LPMM 知识库启用,问答时会使用;
|
||||
- 为 `false`:LPMM 关闭,即使知识库有数据,也不会参与回答。
|
||||
- 修改后需要重启主程序,让设置生效。
|
||||
|
||||
---
|
||||
|
||||
如果你是普通用户,只需要记住一句话:
|
||||
|
||||
> “导入三步走:预处理 → 信息抽取 → 导入 OpenIE;
|
||||
> 删除三步走:先检查 → 再删除 → 然后再检查。”
|
||||
|
||||
照着本指南中的命令一步一步执行,就可以安全地管理你的 LPMM 知识库。***
|
||||
@@ -1,271 +0,0 @@
|
||||
# ⚡ Action组件详解
|
||||
|
||||
## 📖 什么是Action
|
||||
|
||||
Action是给麦麦在回复之外提供额外功能的智能组件,**由麦麦的决策系统自主选择是否使用**,具有随机性和拟人化的调用特点。Action不是直接响应用户命令,而是让麦麦根据聊天情境智能地选择合适的动作,使其行为更加自然和真实。
|
||||
|
||||
### Action的特点
|
||||
|
||||
- 🧠 **智能激活**:麦麦根据多种条件智能判断是否使用
|
||||
- 🎲 **可随机性**:可以使用随机数激活,增加行为的不可预测性,更接近真人交流
|
||||
- 🤖 **拟人化**:让麦麦的回应更自然、更有个性
|
||||
- 🔄 **情境感知**:基于聊天上下文做出合适的反应
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Action组件的基本结构
|
||||
首先,所有的Action都应该继承`BaseAction`类。
|
||||
|
||||
其次,每个Action组件都应该实现以下基本信息:
|
||||
```python
|
||||
class ExampleAction(BaseAction):
|
||||
action_name = "example_action" # 动作的唯一标识符
|
||||
action_description = "这是一个示例动作" # 动作描述
|
||||
activation_type = ActionActivationType.ALWAYS # 这里以 ALWAYS 为例
|
||||
associated_types = ["text", "emoji", ...] # 关联类型
|
||||
parallel_action = False # 是否允许与其他Action并行执行
|
||||
action_parameters = {"param1": "参数1的说明", "param2": "参数2的说明", ...}
|
||||
# Action使用场景描述 - 帮助LLM判断何时"选择"使用
|
||||
action_require = ["使用场景描述1", "使用场景描述2", ...]
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
执行Action的主要逻辑
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str]: (是否成功, 执行结果描述)
|
||||
"""
|
||||
# ---- 执行动作的逻辑 ----
|
||||
return True, "执行成功"
|
||||
```
|
||||
#### associated_types: 该Action会发送的消息类型,例如文本、表情等。
|
||||
|
||||
这部分由Adapter传递给处理器。
|
||||
|
||||
以 MaiBot-Napcat-Adapter 为例,可选项目如下:
|
||||
| 类型 | 说明 | 格式 |
|
||||
| --- | --- | --- |
|
||||
| text | 文本消息 | str |
|
||||
| emoji | 表情消息 | str: 表情包的无头base64|
|
||||
| image | 图片消息 | str: 图片的无头base64 |
|
||||
| reply | 回复消息 | str: 回复的消息ID |
|
||||
| voice | 语音消息 | str: wav格式语音的无头base64 |
|
||||
| command | 命令消息 | 参见Adapter文档 |
|
||||
| voiceurl | 语音URL消息 | str: wav格式语音的URL |
|
||||
| music | 音乐消息 | str: 这首歌在网易云音乐的音乐id |
|
||||
| videourl | 视频URL消息 | str: 视频的URL |
|
||||
| file | 文件消息 | str: 文件的路径 |
|
||||
|
||||
**请知悉,对于不同的处理器,其支持的消息类型可能会有所不同。在开发时请注意。**
|
||||
|
||||
#### action_parameters: 该Action的参数说明。
|
||||
这是一个字典,键为参数名,值为参数说明。这个字段可以帮助LLM理解如何使用这个Action,并由LLM返回对应的参数,最后传递到 Action 的 **`action_data`** 属性中。其格式与你定义的格式完全相同 **(除非LLM哈气了,返回了错误的内容)**。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Action 调用的决策机制
|
||||
|
||||
Action采用**两层决策机制**来优化性能和决策质量:
|
||||
|
||||
> 设计目的:在加载许多插件的时候降低LLM决策压力,避免让麦麦在过多的选项中纠结。
|
||||
|
||||
**第一层:激活控制(Activation Control)**
|
||||
|
||||
激活决定麦麦是否 **“知道”** 这个Action的存在,即这个Action是否进入决策候选池。不被激活的Action麦麦永远不会选择。
|
||||
|
||||
**第二层:使用决策(Usage Decision)**
|
||||
|
||||
在Action被激活后,使用条件决定麦麦什么时候会 **“选择”** 使用这个Action。
|
||||
|
||||
### 决策参数详解 🔧
|
||||
|
||||
#### 第一层:ActivationType 激活类型说明
|
||||
|
||||
| 激活类型 | 说明 | 使用场景 |
|
||||
| ----------- | ---------------------------------------- | ---------------------- |
|
||||
| [`NEVER`](#never-激活) | 从不激活,Action对麦麦不可见 | 临时禁用某个Action |
|
||||
| [`ALWAYS`](#always-激活) | 永远激活,Action总是在麦麦的候选池中 | 核心功能,如回复、不回复 |
|
||||
| `RANDOM` | 基于随机概率决定是否激活 | 增加行为随机性的功能 |
|
||||
| `KEYWORD` | 当检测到特定关键词时激活 | 明确触发条件的功能 |
|
||||
|
||||
#### `NEVER` 激活
|
||||
|
||||
`ActionActivationType.NEVER` 会使得 Action 永远不会被激活
|
||||
|
||||
```python
|
||||
class DisabledAction(BaseAction):
|
||||
activation_type = ActionActivationType.NEVER # 永远不激活
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 这个Action永远不会被执行
|
||||
return False, "这个Action被禁用"
|
||||
```
|
||||
|
||||
#### `ALWAYS` 激活
|
||||
|
||||
`ActionActivationType.ALWAYS` 会使得 Action 永远会被激活,即一直在 Action 候选池中
|
||||
|
||||
这种激活方式常用于核心功能,如回复或不回复。
|
||||
|
||||
```python
|
||||
class AlwaysActivatedAction(BaseAction):
|
||||
activation_type = ActionActivationType.ALWAYS # 永远激活
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 执行核心功能
|
||||
return True, "执行了核心功能"
|
||||
```
|
||||
|
||||
#### `RANDOM` 激活
|
||||
|
||||
`ActionActivationType.RANDOM`会使得这个 Action 根据随机概率决定是否加入候选池。
|
||||
|
||||
概率则由代码中的`random_activation_probability`控制。在内部实现中我们使用了`random.random()`来生成一个0到1之间的随机数,并与这个概率进行比较。
|
||||
|
||||
因此使用这个方法需要实现`random_activation_probability`属性。
|
||||
|
||||
```python
|
||||
class SurpriseAction(BaseAction):
|
||||
activation_type = ActionActivationType.RANDOM # 基于随机概率激活
|
||||
# 随机激活概率
|
||||
random_activation_probability = 0.1 # 10%概率激活
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 执行惊喜动作
|
||||
return True, "发送了惊喜内容"
|
||||
```
|
||||
|
||||
#### `KEYWORD` 激活
|
||||
|
||||
`ActionActivationType.KEYWORD`会使得这个 Action 在检测到特定关键词时激活。
|
||||
|
||||
关键词由代码中的`activation_keywords`定义,而`keyword_case_sensitive`则控制关键词匹配时是否区分大小写。在内部实现中,我们使用了`in`操作符来检查消息内容是否包含这些关键词。
|
||||
|
||||
因此,使用此种方法需要实现`activation_keywords`和`keyword_case_sensitive`属性。
|
||||
|
||||
```python
|
||||
class GreetingAction(BaseAction):
|
||||
activation_type = ActionActivationType.KEYWORD # 关键词激活
|
||||
activation_keywords = ["你好", "hello", "hi", "嗨"] # 关键词配置
|
||||
keyword_case_sensitive = False # 不区分大小写
|
||||
|
||||
async def execute(self) -> Tuple[bool, str]:
|
||||
# 执行问候逻辑
|
||||
return True, "发送了问候"
|
||||
```
|
||||
|
||||
一个完整的使用`ActionActivationType.KEYWORD`的例子请参考`plugins/hello_world_plugin`中的`ByeAction`。
|
||||
|
||||
#### 第二层:使用决策
|
||||
|
||||
**在Action被激活后,使用条件决定麦麦什么时候会"选择"使用这个Action**。
|
||||
|
||||
这一层由以下因素综合决定:
|
||||
|
||||
- `action_require`:使用场景描述,帮助LLM判断何时选择
|
||||
- `action_parameters`:所需参数,影响Action的可执行性
|
||||
- 当前聊天上下文和麦麦的决策逻辑
|
||||
|
||||
---
|
||||
|
||||
### 决策流程示例
|
||||
|
||||
```python
|
||||
class EmojiAction(BaseAction):
|
||||
# 第一层:激活控制
|
||||
activation_type = ActionActivationType.RANDOM # 随机激活
|
||||
random_activation_probability = 0.1 # 10%概率激活
|
||||
|
||||
# 第二层:使用决策
|
||||
action_require = [
|
||||
"表达情绪时可以选择使用",
|
||||
"增加聊天趣味性",
|
||||
"不要连续发送多个表情"
|
||||
]
|
||||
```
|
||||
|
||||
**决策流程**:
|
||||
|
||||
1. **第一层激活判断**:
|
||||
|
||||
- 使用随机数进行决策,当`random.random() < self.random_activation_probability`时,麦麦才"知道"可以使用这个Action
|
||||
2. **第二层使用决策**:
|
||||
|
||||
- 即使Action被激活,麦麦还会根据 `action_require` 中的条件判断是否真正选择使用
|
||||
- 例如:如果刚刚已经发过表情,根据"不要连续发送多个表情"的要求,麦麦可能不会选择这个Action
|
||||
|
||||
---
|
||||
|
||||
## Action 内置属性说明
|
||||
```python
|
||||
class BaseAction:
|
||||
def __init__(self):
|
||||
# 消息相关属性
|
||||
self.log_prefix: str # 日志前缀
|
||||
self.group_id: str # 群组ID
|
||||
self.group_name: str # 群组名称
|
||||
self.user_id: str # 用户ID
|
||||
self.user_nickname: str # 用户昵称
|
||||
self.platform: str # 平台类型 (qq, telegram等)
|
||||
self.chat_id: str # 聊天ID
|
||||
self.chat_stream: ChatStream # 聊天流对象
|
||||
self.is_group: bool # 是否群聊
|
||||
|
||||
# 消息体
|
||||
self.action_message: dict # 消息数据
|
||||
|
||||
# Action相关属性
|
||||
self.action_data: dict # Action执行时的数据
|
||||
self.thinking_id: str # 思考ID
|
||||
```
|
||||
action_message为一个字典,包含的键值对如下(省略了不必要的键值对)
|
||||
|
||||
```python
|
||||
{
|
||||
"message_id": "1234567890", # 消息id,str
|
||||
"time": 1627545600.0, # 时间戳,float
|
||||
"chat_id": "abcdef123456", # 聊天ID,str
|
||||
"reply_to": None, # 回复消息id,str或None
|
||||
"interest_value": 0.85, # 兴趣值,float
|
||||
"is_mentioned": True, # 是否被提及,bool
|
||||
"chat_info_last_active_time": 1627548600.0, # 最后活跃时间,float
|
||||
"processed_plain_text": None, # 处理后的文本,str或None
|
||||
"additional_config": None, # Adapter传来的additional_config,dict或None
|
||||
"is_emoji": False, # 是否为表情,bool
|
||||
"is_picid": False, # 是否为图片ID,bool
|
||||
"is_command": False # 是否为命令,bool
|
||||
}
|
||||
```
|
||||
|
||||
部分值的格式请自行查询数据库。
|
||||
|
||||
---
|
||||
|
||||
## Action 内置方法说明
|
||||
```python
|
||||
class BaseAction:
|
||||
def get_config(self, key: str, default=None):
|
||||
"""获取插件配置值,使用嵌套键访问"""
|
||||
|
||||
async def wait_for_new_message(self, timeout: int = 1200) -> Tuple[bool, str]:
|
||||
"""等待新消息或超时"""
|
||||
|
||||
async def send_text(self, content: str, reply_to: str = "", reply_to_platform_id: str = "", typing: bool = False) -> bool:
|
||||
"""发送文本消息"""
|
||||
|
||||
async def send_emoji(self, emoji_base64: str) -> bool:
|
||||
"""发送表情包"""
|
||||
|
||||
async def send_image(self, image_base64: str) -> bool:
|
||||
"""发送图片"""
|
||||
|
||||
async def send_custom(self, message_type: str, content: str, typing: bool = False, reply_to: str = "") -> bool:
|
||||
"""发送自定义类型消息"""
|
||||
|
||||
async def store_action_info(self, action_build_into_prompt: bool = False, action_prompt_display: str = "", action_done: bool = True) -> None:
|
||||
"""存储动作信息到数据库"""
|
||||
|
||||
async def send_command(self, command_name: str, args: Optional[dict] = None, display_message: str = "", storage_message: bool = True) -> bool:
|
||||
"""发送命令消息"""
|
||||
```
|
||||
具体参数与用法参见`BaseAction`基类的定义。
|
||||
@@ -1,130 +0,0 @@
|
||||
# 聊天API
|
||||
|
||||
聊天API模块专门负责聊天信息的查询和管理,帮助插件获取和管理不同的聊天流。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system import chat_api
|
||||
# 或者
|
||||
from src.plugin_system.apis import chat_api
|
||||
```
|
||||
|
||||
一种**Deprecated**方式:
|
||||
```python
|
||||
from src.plugin_system.apis.chat_api import ChatManager
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 获取所有的聊天流
|
||||
|
||||
```python
|
||||
def get_all_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||
```
|
||||
|
||||
**Args**:
|
||||
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的聊天流。
|
||||
|
||||
**Returns**:
|
||||
- `List[ChatStream]`:聊天流列表
|
||||
|
||||
### 2. 获取群聊聊天流
|
||||
|
||||
```python
|
||||
def get_group_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||
```
|
||||
|
||||
**Args**:
|
||||
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。
|
||||
|
||||
**Returns**:
|
||||
- `List[ChatStream]`:群聊聊天流列表
|
||||
|
||||
### 3. 获取私聊聊天流
|
||||
|
||||
```python
|
||||
def get_private_streams(platform: Optional[str] | SpecialTypes = "qq") -> List[ChatStream]:
|
||||
```
|
||||
|
||||
**Args**:
|
||||
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。
|
||||
|
||||
**Returns**:
|
||||
- `List[ChatStream]`:私聊聊天流列表
|
||||
|
||||
### 4. 根据群ID获取聊天流
|
||||
|
||||
```python
|
||||
def get_stream_by_group_id(group_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
|
||||
```
|
||||
|
||||
**Args**:
|
||||
- `group_id`:群聊ID
|
||||
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的群聊流。
|
||||
|
||||
**Returns**:
|
||||
- `Optional[ChatStream]`:聊天流对象,如果未找到返回None
|
||||
|
||||
### 5. 根据用户ID获取私聊流
|
||||
|
||||
```python
|
||||
def get_stream_by_user_id(user_id: str, platform: Optional[str] | SpecialTypes = "qq") -> Optional[ChatStream]:
|
||||
```
|
||||
|
||||
**Args**:
|
||||
- `user_id`:用户ID
|
||||
- `platform`:平台筛选,默认为"qq",可以使用`SpecialTypes`枚举类中的`SpecialTypes.ALL_PLATFORMS`来获取所有平台的私聊流。
|
||||
|
||||
**Returns**:
|
||||
- `Optional[ChatStream]`:聊天流对象,如果未找到返回None
|
||||
|
||||
### 6. 获取聊天流类型
|
||||
|
||||
```python
|
||||
def get_stream_type(chat_stream: ChatStream) -> str:
|
||||
```
|
||||
|
||||
**Args**:
|
||||
- `chat_stream`:聊天流对象
|
||||
|
||||
**Returns**:
|
||||
- `str`:聊天流类型,可能的值包括`private`(私聊流),`group`(群聊流)以及`unknown`(未知类型)。
|
||||
|
||||
### 7. 获取聊天流信息
|
||||
|
||||
```python
|
||||
def get_stream_info(chat_stream: ChatStream) -> Dict[str, Any]:
|
||||
```
|
||||
|
||||
**Args**:
|
||||
- `chat_stream`:聊天流对象
|
||||
|
||||
**Returns**:
|
||||
- `Dict[str, Any]`:聊天流的详细信息,包括但不限于:
|
||||
- `stream_id`:聊天流ID
|
||||
- `platform`:平台名称
|
||||
- `type`:聊天流类型
|
||||
- `group_id`:群聊ID
|
||||
- `group_name`:群聊名称
|
||||
- `user_id`:用户ID
|
||||
- `user_name`:用户名称
|
||||
|
||||
### 8. 获取聊天流统计摘要
|
||||
|
||||
```python
|
||||
def get_streams_summary() -> Dict[str, int]:
|
||||
```
|
||||
|
||||
**Returns**:
|
||||
- `Dict[str, int]`:聊天流统计信息摘要,包含以下键:
|
||||
- `total_streams`:总聊天流数量
|
||||
- `group_streams`:群聊流数量
|
||||
- `private_streams`:私聊流数量
|
||||
- `qq_streams`:QQ平台流数量
|
||||
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 大部分函数在参数不合法时候会抛出异常,请确保你的程序进行了捕获。
|
||||
2. `ChatStream`对象包含了聊天的完整信息,包括用户信息、群信息等。
|
||||
@@ -1,194 +0,0 @@
|
||||
# 组件管理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. 获取指定 Tool 的注册信息
|
||||
```python
|
||||
def get_registered_tool_info(tool_name: str) -> Optional[ToolInfo]:
|
||||
```
|
||||
获取指定 Tool 的注册信息。
|
||||
|
||||
**Args:**
|
||||
- `tool_name` (str): Tool 名称。
|
||||
|
||||
**Returns:**
|
||||
- `Optional[ToolInfo]` - Tool 信息对象,如果 Tool 不存在则返回 `None`。
|
||||
|
||||
### 9. 获取指定 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`。
|
||||
|
||||
### 10. 全局启用指定组件
|
||||
```python
|
||||
def globally_enable_component(component_name: str, component_type: ComponentType) -> bool:
|
||||
```
|
||||
全局启用指定组件。
|
||||
|
||||
**Args:**
|
||||
- `component_name` (str): 组件名称。
|
||||
- `component_type` (ComponentType): 组件类型。
|
||||
|
||||
**Returns:**
|
||||
- `bool` - 启用成功返回 `True`,否则返回 `False`。
|
||||
|
||||
### 11. 全局禁用指定组件
|
||||
```python
|
||||
async def globally_disable_component(component_name: str, component_type: ComponentType) -> bool:
|
||||
```
|
||||
全局禁用指定组件。
|
||||
|
||||
**此函数是异步的,确保在异步环境中调用。**
|
||||
|
||||
**Args:**
|
||||
- `component_name` (str): 组件名称。
|
||||
- `component_type` (ComponentType): 组件类型。
|
||||
|
||||
**Returns:**
|
||||
- `bool` - 禁用成功返回 `True`,否则返回 `False`。
|
||||
|
||||
### 12. 局部启用指定组件
|
||||
```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`。
|
||||
|
||||
### 13. 局部禁用指定组件
|
||||
```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`。
|
||||
|
||||
### 14. 获取指定消息流中禁用的组件列表
|
||||
```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]` - 禁用的组件名称列表。
|
||||
@@ -1,52 +0,0 @@
|
||||
# 配置API
|
||||
|
||||
配置API模块提供了配置读取功能,让插件能够安全地访问全局配置和插件配置。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import config_api
|
||||
# 或者
|
||||
from src.plugin_system import config_api
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 访问全局配置
|
||||
|
||||
```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")
|
||||
```
|
||||
|
||||
### 2. 获取插件配置
|
||||
|
||||
```python
|
||||
def get_plugin_config(plugin_config: dict, key: str, default: Any = None) -> Any:
|
||||
```
|
||||
**Args**:
|
||||
- `plugin_config`: 插件配置字典
|
||||
- `key`: 配置键名,支持嵌套访问如 "section.subsection.key",大小写敏感
|
||||
- `default`: 如果配置不存在时返回的默认值
|
||||
|
||||
**Returns**:
|
||||
- `Any`: 配置值或默认值
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **只读访问**:配置API只提供读取功能,插件不能修改全局配置
|
||||
2. **错误处理**:所有函数都有错误处理,失败时会记录日志并返回默认值
|
||||
3. **安全性**:插件通过此API访问配置是安全和隔离的
|
||||
4. **性能**:频繁访问的配置建议在插件初始化时获取并缓存
|
||||
@@ -1,216 +0,0 @@
|
||||
# 数据库API
|
||||
|
||||
数据库API模块提供通用的数据库操作功能,支持查询、创建、更新和删除记录,采用Peewee ORM模型。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import database_api
|
||||
# 或者
|
||||
from src.plugin_system import database_api
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 通用数据库操作
|
||||
|
||||
```python
|
||||
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]:
|
||||
```
|
||||
执行数据库查询操作的通用接口。
|
||||
|
||||
**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",
|
||||
filters={"chat_id": chat_stream.stream_id},
|
||||
limit=10,
|
||||
order_by=["-time"]
|
||||
)
|
||||
```
|
||||
2. 创建一条记录
|
||||
```python
|
||||
new_record = await database_api.db_query(
|
||||
ActionRecords,
|
||||
data={"action_id": "123", "time": time.time(), "action_name": "TestAction"},
|
||||
query_type="create",
|
||||
)
|
||||
```
|
||||
3. 更新记录
|
||||
```python
|
||||
updated_count = await database_api.db_query(
|
||||
ActionRecords,
|
||||
data={"action_done": True},
|
||||
query_type="update",
|
||||
filters={"action_id": "123"},
|
||||
)
|
||||
```
|
||||
4. 删除记录
|
||||
```python
|
||||
deleted_count = await database_api.db_query(
|
||||
ActionRecords,
|
||||
query_type="delete",
|
||||
filters={"action_id": "123"}
|
||||
)
|
||||
```
|
||||
5. 计数
|
||||
```python
|
||||
count = await database_api.db_query(
|
||||
Messages,
|
||||
query_type="count",
|
||||
filters={"chat_id": chat_stream.stream_id}
|
||||
)
|
||||
```
|
||||
|
||||
### 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
|
||||
record = await database_api.db_save(
|
||||
ActionRecords,
|
||||
{
|
||||
"action_id": "123",
|
||||
"time": time.time(),
|
||||
"action_name": "TestAction",
|
||||
"action_done": True
|
||||
},
|
||||
key_field="action_id",
|
||||
key_value="123"
|
||||
)
|
||||
```
|
||||
|
||||
### 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",
|
||||
)
|
||||
```
|
||||
|
||||
### 4. 动作信息存储
|
||||
```python
|
||||
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
|
||||
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"
|
||||
)
|
||||
```
|
||||
@@ -1,141 +0,0 @@
|
||||
# 表情包API
|
||||
|
||||
表情包API模块提供表情包的获取、查询和管理功能,让插件能够智能地选择和使用表情包。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import emoji_api
|
||||
# 或者
|
||||
from src.plugin_system import emoji_api
|
||||
```
|
||||
|
||||
## 二步走识别优化
|
||||
|
||||
从新版本开始,表情包识别系统采用了**二步走识别 + 智能缓存**的优化方案:
|
||||
|
||||
### **收到表情包时的识别流程**
|
||||
1. **第一步**:VLM视觉分析 - 生成详细描述
|
||||
2. **第二步**:LLM情感分析 - 基于详细描述提取核心情感标签
|
||||
3. **缓存机制**:将情感标签缓存到数据库,详细描述保存到Images表
|
||||
|
||||
### **注册表情包时的优化**
|
||||
- **智能复用**:优先从Images表获取已有的详细描述
|
||||
- **避免重复**:如果表情包之前被收到过,跳过VLM调用
|
||||
- **性能提升**:减少不必要的AI调用,降低延时和成本
|
||||
|
||||
### **缓存策略**
|
||||
- **ImageDescriptions表**:缓存最终的情感标签(用于快速显示)
|
||||
- **Images表**:保存详细描述(用于注册时复用)
|
||||
- **双重检查**:防止并发情况下的重复生成
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 表情包获取
|
||||
```python
|
||||
async def get_by_description(description: str) -> Optional[Tuple[str, str, str]]:
|
||||
```
|
||||
根据场景描述选择表情包
|
||||
|
||||
**Args:**
|
||||
- `description`:表情包的描述文本,例如"开心"、"难过"、"愤怒"等
|
||||
|
||||
**Returns:**
|
||||
- `Optional[Tuple[str, str, str]]`:一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到匹配的表情包则返回None
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
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用于发送表情包
|
||||
```
|
||||
|
||||
### 2. 随机获取表情包
|
||||
```python
|
||||
async def get_random(count: Optional[int] = 1) -> List[Tuple[str, str, str]]:
|
||||
```
|
||||
随机获取指定数量的表情包
|
||||
|
||||
**Args:**
|
||||
- `count`:要获取的表情包数量,默认为1
|
||||
|
||||
**Returns:**
|
||||
- `List[Tuple[str, str, str]]`:一个包含多个表情包的列表,每个元素是一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到或出错则返回空列表
|
||||
|
||||
### 3. 根据情感获取表情包
|
||||
```python
|
||||
async def get_by_emotion(emotion: str) -> Optional[Tuple[str, str, str]]:
|
||||
```
|
||||
根据情感标签获取表情包
|
||||
|
||||
**Args:**
|
||||
- `emotion`:情感标签,例如"开心"、"悲伤"、"愤怒"等
|
||||
|
||||
**Returns:**
|
||||
- `Optional[Tuple[str, str, str]]`:一个元组: (表情包的base64编码, 描述, 情感标签),如果未找到则返回None
|
||||
|
||||
### 4. 获取表情包数量
|
||||
```python
|
||||
def get_count() -> int:
|
||||
```
|
||||
获取当前可用表情包的数量
|
||||
|
||||
### 5. 获取表情包系统信息
|
||||
```python
|
||||
def get_info() -> Dict[str, Any]:
|
||||
```
|
||||
获取表情包系统的基本信息
|
||||
|
||||
**Returns:**
|
||||
- `Dict[str, Any]`:包含表情包数量、描述等信息的字典,包含以下键:
|
||||
- `current_count`:当前表情包数量
|
||||
- `max_count`:最大表情包数量
|
||||
- `available_emojis`:当前可用的表情包数量
|
||||
|
||||
### 6. 获取所有可用的情感标签
|
||||
```python
|
||||
def get_emotions() -> List[str]:
|
||||
```
|
||||
获取所有可用的情感标签 **(已经去重)**
|
||||
|
||||
### 7. 获取所有表情包描述
|
||||
```python
|
||||
def get_descriptions() -> List[str]:
|
||||
```
|
||||
获取所有表情包的描述列表
|
||||
|
||||
## 场景描述说明
|
||||
|
||||
### 常用场景描述
|
||||
表情包系统支持多种具体的场景描述,举例如下:
|
||||
|
||||
- **开心类场景**:开心的大笑、满意的微笑、兴奋的手舞足蹈
|
||||
- **无奈类场景**:表示无奈和沮丧、轻微的讽刺、无语的摇头
|
||||
- **愤怒类场景**:愤怒和不满、生气的瞪视、暴躁的抓狂
|
||||
- **惊讶类场景**:震惊的表情、意外的发现、困惑的思考
|
||||
- **可爱类场景**:卖萌的表情、撒娇的动作、害羞的样子
|
||||
|
||||
### 情感关键词示例
|
||||
系统支持的情感关键词举例如下:
|
||||
- 大笑、微笑、兴奋、手舞足蹈
|
||||
- 无奈、沮丧、讽刺、无语、摇头
|
||||
- 愤怒、不满、生气、瞪视、抓狂
|
||||
- 震惊、意外、困惑、思考
|
||||
- 卖萌、撒娇、害羞、可爱
|
||||
|
||||
### 匹配机制
|
||||
- **精确匹配**:优先匹配完整的场景描述,如"开心的大笑"
|
||||
- **关键词匹配**:如果没有精确匹配,则根据关键词进行模糊匹配
|
||||
- **语义匹配**:系统会理解场景的语义含义进行智能匹配
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **异步函数**:部分函数是异步的,需要使用 `await`
|
||||
2. **返回格式**:表情包以base64编码返回,可直接用于发送
|
||||
3. **错误处理**:所有函数都有错误处理,失败时返回None,空列表或默认值
|
||||
4. **使用统计**:系统会记录表情包的使用次数
|
||||
5. **文件依赖**:表情包依赖于本地文件,确保表情包文件存在
|
||||
6. **编码格式**:返回的是base64编码的图片数据,可直接用于网络传输
|
||||
7. **场景理解**:系统能理解具体的场景描述,比简单的情感分类更准确
|
||||
@@ -1,198 +0,0 @@
|
||||
# 回复生成器API
|
||||
|
||||
回复生成器API模块提供智能回复生成功能,让插件能够使用系统的回复生成器来产生自然的聊天回复。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import generator_api
|
||||
# 或者
|
||||
from src.plugin_system import generator_api
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 回复器获取
|
||||
```python
|
||||
def get_replyer(
|
||||
chat_stream: Optional[ChatStream] = None,
|
||||
chat_id: Optional[str] = None,
|
||||
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
|
||||
request_type: str = "replyer",
|
||||
) -> Optional[DefaultReplyer]:
|
||||
```
|
||||
获取回复器对象
|
||||
|
||||
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||
|
||||
使用 ReplyerManager 来管理实例,避免重复创建。
|
||||
|
||||
**Args:**
|
||||
- `chat_stream`: 聊天流对象
|
||||
- `chat_id`: 聊天ID(实际上就是`stream_id`)
|
||||
- `model_set_with_weight`: 模型配置列表,每个元素为 `(TaskConfig, weight)` 元组
|
||||
- `request_type`: 请求类型,用于记录LLM使用情况,可以不写
|
||||
|
||||
**Returns:**
|
||||
- `DefaultReplyer`: 回复器对象,如果获取失败则返回None
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
# 使用聊天流获取回复器
|
||||
replyer = generator_api.get_replyer(chat_stream=chat_stream)
|
||||
|
||||
# 使用平台和ID获取回复器
|
||||
replyer = generator_api.get_replyer(chat_id="123456789")
|
||||
```
|
||||
|
||||
### 2. 回复生成
|
||||
```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_splitter: bool = True,
|
||||
enable_chinese_typo: bool = True,
|
||||
return_prompt: bool = False,
|
||||
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
|
||||
request_type: str = "generator_api",
|
||||
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
|
||||
```
|
||||
生成回复
|
||||
|
||||
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||
|
||||
**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_splitter`: 是否启用分割器
|
||||
- `enable_chinese_typo`: 是否启用中文错别字
|
||||
- `return_prompt`: 是否返回提示词
|
||||
- `model_set_with_weight`: 模型配置列表,每个元素为 `(TaskConfig, weight)` 元组
|
||||
- `request_type`: 请求类型(可选,记录LLM使用)
|
||||
- `request_type`: 请求类型,用于记录LLM使用情况
|
||||
|
||||
**Returns:**
|
||||
- `Tuple[bool, List[Tuple[str, Any]], Optional[str]]`: (是否成功, 回复集合, 提示词)
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
success, reply_set, prompt = await generator_api.generate_reply(
|
||||
chat_stream=chat_stream,
|
||||
action_data=action_data,
|
||||
reply_to="麦麦:你好",
|
||||
available_actions=action_info,
|
||||
return_prompt=True
|
||||
)
|
||||
if success:
|
||||
for reply_type, reply_content in reply_set:
|
||||
print(f"回复类型: {reply_type}, 内容: {reply_content}")
|
||||
if prompt:
|
||||
print(f"使用的提示词: {prompt}")
|
||||
```
|
||||
|
||||
### 3. 回复重写
|
||||
```python
|
||||
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_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
|
||||
raw_reply: str = "",
|
||||
reason: str = "",
|
||||
reply_to: str = "",
|
||||
return_prompt: bool = False,
|
||||
) -> Tuple[bool, List[Tuple[str, Any]], Optional[str]]:
|
||||
```
|
||||
重写回复,使用新的内容替换旧的回复内容。
|
||||
|
||||
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||
|
||||
**Args:**
|
||||
- `chat_stream`: 聊天流对象
|
||||
- `reply_data`: 回复数据,包含`raw_reply`, `reason`和`reply_to`,**(向下兼容备用,当其他参数缺失时从此获取)**
|
||||
- `chat_id`: 聊天ID(实际上就是`stream_id`)
|
||||
- `enable_splitter`: 是否启用分割器
|
||||
- `enable_chinese_typo`: 是否启用中文错别字
|
||||
- `model_set_with_weight`: 模型配置列表,每个元素为 (TaskConfig, weight) 元组
|
||||
- `raw_reply`: 原始回复内容
|
||||
- `reason`: 重写原因
|
||||
- `reply_to`: 回复目标,格式为 `{发送者的person_name:消息内容}`
|
||||
|
||||
**Returns:**
|
||||
- `Tuple[bool, List[Tuple[str, Any]], Optional[str]]`: (是否成功, 回复集合, 提示词)
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
success, reply_set, prompt = await generator_api.rewrite_reply(
|
||||
chat_stream=chat_stream,
|
||||
raw_reply="原始回复内容",
|
||||
reason="重写原因",
|
||||
reply_to="麦麦:你好",
|
||||
return_prompt=True
|
||||
)
|
||||
if success:
|
||||
for reply_type, reply_content in reply_set:
|
||||
print(f"回复类型: {reply_type}, 内容: {reply_content}")
|
||||
if prompt:
|
||||
print(f"使用的提示词: {prompt}")
|
||||
```
|
||||
|
||||
## 回复集合`reply_set`格式
|
||||
|
||||
### 回复类型
|
||||
生成的回复集合包含多种类型的回复:
|
||||
|
||||
- `"text"`:纯文本回复
|
||||
- `"emoji"`:表情包回复
|
||||
- `"image"`:图片回复
|
||||
- `"mixed"`:混合类型回复
|
||||
|
||||
### 回复集合结构
|
||||
```python
|
||||
# 示例回复集合
|
||||
reply_set = [
|
||||
("text", "很高兴见到你!"),
|
||||
("emoji", "emoji_base64_data"),
|
||||
("text", "有什么可以帮助你的吗?")
|
||||
]
|
||||
```
|
||||
|
||||
### 4. 自定义提示词回复
|
||||
```python
|
||||
async def generate_response_custom(
|
||||
chat_stream: Optional[ChatStream] = None,
|
||||
chat_id: Optional[str] = None,
|
||||
model_set_with_weight: Optional[List[Tuple[TaskConfig, float]]] = None,
|
||||
prompt: str = "",
|
||||
) -> Optional[str]:
|
||||
```
|
||||
生成自定义提示词回复
|
||||
|
||||
优先使用chat_stream,如果没有则使用chat_id直接查找。
|
||||
|
||||
**Args:**
|
||||
- `chat_stream`: 聊天流对象
|
||||
- `chat_id`: 聊天ID(备用)
|
||||
- `model_set_with_weight`: 模型集合配置列表
|
||||
- `prompt`: 自定义提示词
|
||||
|
||||
**Returns:**
|
||||
- `Optional[str]`: 生成的自定义回复内容,如果生成失败则返回None
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **异步操作**:部分函数是异步的,须使用`await`
|
||||
2. **聊天流依赖**:需要有效的聊天流对象才能正常工作
|
||||
3. **性能考虑**:回复生成可能需要一些时间,特别是使用LLM时
|
||||
4. **回复格式**:返回的回复集合是元组列表,包含类型和内容
|
||||
5. **上下文感知**:生成器会考虑聊天上下文和历史消息,除非你用的是自定义提示词。
|
||||
@@ -1,65 +0,0 @@
|
||||
# LLM API
|
||||
|
||||
LLM API模块提供与大语言模型交互的功能,让插件能够使用系统配置的LLM模型进行内容生成。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import llm_api
|
||||
# 或者
|
||||
from src.plugin_system import llm_api
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 查询可用模型
|
||||
```python
|
||||
def get_available_models() -> Dict[str, TaskConfig]:
|
||||
```
|
||||
获取所有可用的模型配置。
|
||||
|
||||
**Return:**
|
||||
- `Dict[str, TaskConfig]`:模型配置字典,key为模型名称,value为模型配置对象。
|
||||
|
||||
### 2. 使用模型生成内容
|
||||
```python
|
||||
async def generate_with_model(
|
||||
prompt: str,
|
||||
model_config: TaskConfig,
|
||||
request_type: str = "plugin.generate",
|
||||
temperature: Optional[float] = None,
|
||||
max_tokens: Optional[int] = None,
|
||||
) -> Tuple[bool, str, str, str]:
|
||||
```
|
||||
使用指定模型生成内容。
|
||||
|
||||
**Args:**
|
||||
- `prompt`:提示词。
|
||||
- `model_config`:模型配置对象(从 `get_available_models` 获取)。
|
||||
- `request_type`:请求类型标识,默认为 `"plugin.generate"`。
|
||||
- `temperature`:生成内容的温度设置,影响输出的随机性。
|
||||
- `max_tokens`:生成内容的最大token数。
|
||||
|
||||
**Return:**
|
||||
- `Tuple[bool, str, str, str]`:返回一个元组,包含(是否成功, 生成的内容, 推理过程, 模型名称)。
|
||||
|
||||
### 3. 有Tool情况下使用模型生成内容
|
||||
```python
|
||||
async def generate_with_model_with_tools(
|
||||
prompt: str,
|
||||
model_config: TaskConfig,
|
||||
tool_options: List[Dict[str, Any]] | None = None,
|
||||
request_type: str = "plugin.generate",
|
||||
temperature: Optional[float] = None,
|
||||
max_tokens: Optional[int] = None,
|
||||
) -> Tuple[bool, str, str, str, List[ToolCall] | None]:
|
||||
```
|
||||
使用指定模型生成内容,并支持工具调用。
|
||||
|
||||
**Args:**
|
||||
- `prompt`:提示词。
|
||||
- `model_config`:模型配置对象(从 `get_available_models` 获取)。
|
||||
- `tool_options`:工具选项列表,包含可用工具的配置,字典为每一个工具的定义,参见[tool-components.md](../tool-components.md#属性说明),可用`tool_api.get_llm_available_tool_definitions()`获取并选择。
|
||||
- `request_type`:请求类型标识,默认为 `"plugin.generate"`。
|
||||
- `temperature`:生成内容的温度设置,影响输出的随机性。
|
||||
- `max_tokens`:生成内容的最大token数。
|
||||
@@ -1,29 +0,0 @@
|
||||
# Logging API
|
||||
|
||||
Logging API模块提供了获取本体logger的功能,允许插件记录日志信息。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import get_logger
|
||||
# 或者
|
||||
from src.plugin_system import get_logger
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
### 1. 获取本体logger
|
||||
```python
|
||||
def get_logger(name: str) -> structlog.stdlib.BoundLogger:
|
||||
```
|
||||
获取本体logger实例。
|
||||
|
||||
**Args:**
|
||||
- `name` (str): 日志记录器的名称。
|
||||
|
||||
**Returns:**
|
||||
- 一个logger实例,有以下方法:
|
||||
- `debug`
|
||||
- `info`
|
||||
- `warning`
|
||||
- `error`
|
||||
- `critical`
|
||||
@@ -1,372 +0,0 @@
|
||||
# 消息API
|
||||
|
||||
消息API提供了强大的消息查询、计数和格式化功能,让你轻松处理聊天消息数据。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import message_api
|
||||
# 或者
|
||||
from src.plugin_system import message_api
|
||||
```
|
||||
|
||||
## 功能概述
|
||||
|
||||
消息API主要提供三大类功能:
|
||||
- **消息查询** - 按时间、聊天、用户等条件查询消息
|
||||
- **消息计数** - 统计新消息数量
|
||||
- **消息格式化** - 将消息转换为可读格式
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 按照事件查询消息
|
||||
```python
|
||||
def get_messages_by_time(
|
||||
start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False
|
||||
) -> List[Dict[str, Any]]:
|
||||
```
|
||||
获取指定时间范围内的消息。
|
||||
|
||||
**Args:**
|
||||
- `start_time` (float): 开始时间戳
|
||||
- `end_time` (float): 结束时间戳
|
||||
- `limit` (int): 限制返回消息数量,0为不限制
|
||||
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||
|
||||
**Returns:**
|
||||
- `List[Dict[str, Any]]` - 消息列表
|
||||
|
||||
消息列表中包含的键与`Messages`类的属性一致。(位于`src.common.database.database_model`)
|
||||
|
||||
### 2. 获取指定聊天中指定时间范围内的信息
|
||||
```python
|
||||
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]]:
|
||||
```
|
||||
获取指定聊天中指定时间范围内的消息。
|
||||
|
||||
**Args:**
|
||||
- `chat_id` (str): 聊天ID
|
||||
- `start_time` (float): 开始时间戳
|
||||
- `end_time` (float): 结束时间戳
|
||||
- `limit` (int): 限制返回消息数量,0为不限制
|
||||
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||
|
||||
**Returns:**
|
||||
- `List[Dict[str, Any]]` - 消息列表
|
||||
|
||||
|
||||
### 3. 获取指定聊天中指定时间范围内的信息(包含边界)
|
||||
```python
|
||||
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]]:
|
||||
```
|
||||
获取指定聊天中指定时间范围内的消息(包含边界)。
|
||||
|
||||
**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]]` - 消息列表
|
||||
|
||||
|
||||
### 4. 获取指定聊天中指定用户在指定时间范围内的消息
|
||||
```python
|
||||
def get_messages_by_time_in_chat_for_users(
|
||||
chat_id: str,
|
||||
start_time: float,
|
||||
end_time: float,
|
||||
person_ids: List[str],
|
||||
limit: int = 0,
|
||||
limit_mode: str = "latest",
|
||||
) -> List[Dict[str, Any]]:
|
||||
```
|
||||
获取指定聊天中指定用户在指定时间范围内的消息。
|
||||
|
||||
**Args:**
|
||||
- `chat_id` (str): 聊天ID
|
||||
- `start_time` (float): 开始时间戳
|
||||
- `end_time` (float): 结束时间戳
|
||||
- `person_ids` (List[str]): 用户ID列表
|
||||
- `limit` (int): 限制返回消息数量,0为不限制
|
||||
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||
|
||||
**Returns:**
|
||||
- `List[Dict[str, Any]]` - 消息列表
|
||||
|
||||
|
||||
### 5. 随机选择一个聊天,返回该聊天在指定时间范围内的消息
|
||||
```python
|
||||
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]]:
|
||||
```
|
||||
随机选择一个聊天,返回该聊天在指定时间范围内的消息。
|
||||
|
||||
**Args:**
|
||||
- `start_time` (float): 开始时间戳
|
||||
- `end_time` (float): 结束时间戳
|
||||
- `limit` (int): 限制返回消息数量,0为不限制
|
||||
- `limit_mode` (str): 限制模式,`"earliest"`获取最早记录,`"latest"`获取最新记录
|
||||
- `filter_mai` (bool): 是否过滤掉机器人的消息,默认False
|
||||
|
||||
**Returns:**
|
||||
- `List[Dict[str, Any]]` - 消息列表
|
||||
|
||||
|
||||
### 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]]:
|
||||
```
|
||||
获取指定用户在所有聊天中指定时间范围内的消息。
|
||||
|
||||
**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): 是否将机器人的名称替换为"你"
|
||||
- `merge_messages` (bool): 是否合并连续消息
|
||||
- `timestamp_mode` (str): 时间戳显示模式,`"relative"`或`"absolute"`
|
||||
- `read_mark` (float): 已读标记时间戳,用于分割已读和未读消息
|
||||
- `truncate` (bool): 是否截断长消息
|
||||
- `show_actions` (bool): 是否显示动作记录
|
||||
|
||||
**Returns:**
|
||||
- `str` - 格式化后的可读字符串
|
||||
|
||||
|
||||
### 14. 将消息列表构建成可读的字符串,并返回详细信息
|
||||
```python
|
||||
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]]]:
|
||||
```
|
||||
将消息列表构建成可读的字符串,并返回详细信息。
|
||||
|
||||
**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]]]` - 格式化后的可读字符串和详细信息元组列表(时间戳, 昵称, 内容)
|
||||
|
||||
|
||||
### 15. 从消息列表中提取不重复的用户ID列表
|
||||
```python
|
||||
async def get_person_ids_from_messages(
|
||||
messages: List[Dict[str, Any]],
|
||||
) -> List[str]:
|
||||
```
|
||||
从消息列表中提取不重复的用户ID列表。
|
||||
|
||||
**Args:**
|
||||
- `messages` (List[Dict[str, Any]]): 消息列表
|
||||
|
||||
**Returns:**
|
||||
- `List[str]` - 用户ID列表
|
||||
|
||||
|
||||
### 16. 从消息列表中移除机器人的消息
|
||||
```python
|
||||
def filter_mai_messages(
|
||||
messages: List[Dict[str, Any]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
```
|
||||
从消息列表中移除机器人的消息。
|
||||
|
||||
**Args:**
|
||||
- `messages` (List[Dict[str, Any]]): 消息列表,每个元素是消息字典
|
||||
|
||||
**Returns:**
|
||||
- `List[Dict[str, Any]]` - 过滤后的消息列表
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **时间戳格式**:所有时间参数都使用Unix时间戳(float类型)
|
||||
2. **异步函数**:部分函数是异步函数,需要使用 `await`
|
||||
3. **性能考虑**:查询大量消息时建议设置合理的 `limit` 参数
|
||||
4. **消息格式**:返回的消息是字典格式,包含时间戳、发送者、内容等信息
|
||||
5. **用户ID**:`person_ids` 参数接受字符串列表,用于筛选特定用户的消息
|
||||
@@ -1,119 +0,0 @@
|
||||
# 个人信息API
|
||||
|
||||
个人信息API模块提供用户信息查询和管理功能,让插件能够获取和使用用户的相关信息。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import person_api
|
||||
# 或者
|
||||
from src.plugin_system import person_api
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 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_id(MD5哈希值)
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
person_id = person_api.get_person_id("qq", 123456)
|
||||
```
|
||||
|
||||
### 2. 用户信息查询
|
||||
```python
|
||||
async def get_person_value(person_id: str, field_name: str, default: Any = None) -> Any:
|
||||
```
|
||||
查询单个用户信息字段值
|
||||
|
||||
**Args:**
|
||||
- `person_id`:用户的唯一标识ID
|
||||
- `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")
|
||||
```
|
||||
|
||||
### 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,
|
||||
["nickname", "impression", "know_times"],
|
||||
{"nickname": "未知用户", "know_times": 0}
|
||||
)
|
||||
```
|
||||
|
||||
### 4. 判断用户是否已知
|
||||
```python
|
||||
async def is_person_known(platform: str, user_id: int) -> bool:
|
||||
```
|
||||
判断是否认识某个用户
|
||||
|
||||
**Args:**
|
||||
- `platform`:平台名称
|
||||
- `user_id`:用户ID
|
||||
|
||||
**Returns:**
|
||||
- `bool`:是否认识该用户
|
||||
|
||||
### 5. 根据用户名获取Person ID
|
||||
```python
|
||||
def get_person_id_by_name(person_name: str) -> str:
|
||||
```
|
||||
根据用户名获取person_id
|
||||
|
||||
**Args:**
|
||||
- `person_name`:用户名
|
||||
|
||||
**Returns:**
|
||||
- `str`:person_id,如果未找到返回空字符串
|
||||
|
||||
## 常用字段说明
|
||||
|
||||
### 基础信息字段
|
||||
- `nickname`:用户昵称
|
||||
- `platform`:平台信息
|
||||
- `user_id`:用户ID
|
||||
|
||||
### 关系信息字段
|
||||
- `impression`:对用户的印象
|
||||
- `points`: 用户特征点
|
||||
|
||||
其他字段可以参考`PersonInfo`类的属性(位于`src.common.database.database_model`)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **异步操作**:部分查询函数都是异步的,需要使用`await`
|
||||
2. **性能考虑**:批量查询优于单个查询
|
||||
3. **隐私保护**:确保用户信息的使用符合隐私政策
|
||||
4. **数据一致性**:person_id是用户的唯一标识,应妥善保存和使用
|
||||
@@ -1,105 +0,0 @@
|
||||
# 插件管理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]` - 成功加载的插件数量和失败的插件数量。
|
||||
@@ -1,175 +0,0 @@
|
||||
# 消息发送API
|
||||
|
||||
消息发送API模块专门负责发送各种类型的消息,支持文本、表情包、图片等多种消息类型。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import send_api
|
||||
# 或者
|
||||
from src.plugin_system import send_api
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 发送文本消息
|
||||
```python
|
||||
async def text_to_stream(
|
||||
text: str,
|
||||
stream_id: str,
|
||||
typing: bool = False,
|
||||
reply_to: str = "",
|
||||
storage_message: bool = True,
|
||||
) -> bool:
|
||||
```
|
||||
发送文本消息到指定的流
|
||||
|
||||
**Args:**
|
||||
- `text` (str): 要发送的文本内容
|
||||
- `stream_id` (str): 聊天流ID
|
||||
- `typing` (bool): 是否显示正在输入
|
||||
- `reply_to` (str): 回复消息,格式为"发送者:消息内容"
|
||||
- `storage_message` (bool): 是否存储消息到数据库
|
||||
|
||||
**Returns:**
|
||||
- `bool` - 是否发送成功
|
||||
|
||||
### 2. 发送表情包
|
||||
```python
|
||||
async def emoji_to_stream(emoji_base64: str, stream_id: str, storage_message: bool = True) -> bool:
|
||||
```
|
||||
向指定流发送表情包。
|
||||
|
||||
**Args:**
|
||||
- `emoji_base64` (str): 表情包的base64编码
|
||||
- `stream_id` (str): 聊天流ID
|
||||
- `storage_message` (bool): 是否存储消息到数据库
|
||||
|
||||
**Returns:**
|
||||
- `bool` - 是否发送成功
|
||||
|
||||
### 3. 发送图片
|
||||
```python
|
||||
async def image_to_stream(image_base64: str, stream_id: str, storage_message: bool = True) -> bool:
|
||||
```
|
||||
向指定流发送图片。
|
||||
|
||||
**Args:**
|
||||
- `image_base64` (str): 图片的base64编码
|
||||
- `stream_id` (str): 聊天流ID
|
||||
- `storage_message` (bool): 是否存储消息到数据库
|
||||
|
||||
**Returns:**
|
||||
- `bool` - 是否发送成功
|
||||
|
||||
### 4. 发送命令
|
||||
```python
|
||||
async def command_to_stream(command: Union[str, dict], stream_id: str, storage_message: bool = True, display_message: str = "") -> bool:
|
||||
```
|
||||
向指定流发送命令。
|
||||
|
||||
**Args:**
|
||||
- `command` (Union[str, dict]): 命令内容
|
||||
- `stream_id` (str): 聊天流ID
|
||||
- `storage_message` (bool): 是否存储消息到数据库
|
||||
- `display_message` (str): 显示消息
|
||||
|
||||
**Returns:**
|
||||
- `bool` - 是否发送成功
|
||||
|
||||
### 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:
|
||||
```
|
||||
向指定流发送自定义类型消息。
|
||||
|
||||
**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): 是否显示日志
|
||||
|
||||
**Returns:**
|
||||
- `bool` - 是否发送成功
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 基础文本发送,并回复消息
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import send_api
|
||||
|
||||
async def send_hello(chat_stream):
|
||||
"""发送问候消息"""
|
||||
|
||||
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
|
||||
from src.plugin_system.apis import emoji_api
|
||||
async def send_emoji_reaction(chat_stream, emotion):
|
||||
"""根据情感发送表情包"""
|
||||
# 获取表情包
|
||||
emoji_result = await emoji_api.get_by_emotion(emotion)
|
||||
if not emoji_result:
|
||||
return False
|
||||
|
||||
emoji_base64, description, matched_emotion = emoji_result
|
||||
|
||||
# 发送表情包
|
||||
success = await send_api.emoji_to_stream(
|
||||
emoji_base64=emoji_base64,
|
||||
stream_id=chat_stream.stream_id,
|
||||
storage_message=False # 不存储到数据库
|
||||
)
|
||||
|
||||
return success
|
||||
```
|
||||
|
||||
## 消息类型说明
|
||||
|
||||
### 支持的消息类型
|
||||
- `"text"`:纯文本消息
|
||||
- `"emoji"`:表情包消息
|
||||
- `"image"`:图片消息
|
||||
- `"command"`:命令消息
|
||||
- `"video"`:视频消息(如果支持)
|
||||
- `"audio"`:音频消息(如果支持)
|
||||
|
||||
### 回复格式
|
||||
回复消息使用格式:`"发送者:消息内容"` 或 `"发送者:消息内容"`
|
||||
|
||||
系统会自动查找匹配的原始消息并进行回复。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **异步操作**:所有发送函数都是异步的,必须使用`await`
|
||||
2. **错误处理**:发送失败时返回False,成功时返回True
|
||||
3. **发送频率**:注意控制发送频率,避免被平台限制
|
||||
4. **内容限制**:注意平台对消息内容和长度的限制
|
||||
5. **权限检查**:确保机器人有发送消息的权限
|
||||
6. **编码格式**:图片和表情包需要使用base64编码
|
||||
7. **存储选项**:可以选择是否将发送的消息存储到数据库
|
||||
@@ -1,55 +0,0 @@
|
||||
# 工具API
|
||||
|
||||
工具API模块提供了获取和管理工具实例的功能,让插件能够访问系统中注册的工具。
|
||||
|
||||
## 导入方式
|
||||
|
||||
```python
|
||||
from src.plugin_system.apis import tool_api
|
||||
# 或者
|
||||
from src.plugin_system import tool_api
|
||||
```
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 获取工具实例
|
||||
|
||||
```python
|
||||
def get_tool_instance(tool_name: str) -> Optional[BaseTool]:
|
||||
```
|
||||
|
||||
获取指定名称的工具实例。
|
||||
|
||||
**Args**:
|
||||
- `tool_name`: 工具名称字符串
|
||||
|
||||
**Returns**:
|
||||
- `Optional[BaseTool]`: 工具实例,如果工具不存在则返回 None
|
||||
|
||||
### 2. 获取LLM可用的工具定义
|
||||
|
||||
```python
|
||||
def get_llm_available_tool_definitions():
|
||||
```
|
||||
|
||||
获取所有LLM可用的工具定义列表。
|
||||
|
||||
**Returns**:
|
||||
- `List[Tuple[str, Dict[str, Any]]]`: 工具定义列表,每个元素为 `(工具名称, 工具定义字典)` 的元组
|
||||
- 其具体定义请参照[tool-components.md](../tool-components.md#属性说明)中的工具定义格式。
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
# 获取所有LLM可用的工具定义
|
||||
tools = tool_api.get_llm_available_tool_definitions()
|
||||
for tool_name, tool_definition in tools:
|
||||
print(f"工具: {tool_name}")
|
||||
print(f"定义: {tool_definition}")
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **工具存在性检查**:使用前请检查工具实例是否为 None
|
||||
2. **权限控制**:某些工具可能有使用权限限制
|
||||
3. **异步调用**:大多数工具方法是异步的,需要使用 await
|
||||
4. **错误处理**:调用工具时请做好异常处理
|
||||
@@ -1,89 +0,0 @@
|
||||
# 💻 Command组件详解
|
||||
|
||||
## 📖 什么是Command
|
||||
|
||||
Command是直接响应用户明确指令的组件,与Action不同,Command是**被动触发**的,当用户输入特定格式的命令时立即执行。
|
||||
|
||||
Command通过正则表达式匹配用户输入,提供确定性的功能服务。
|
||||
|
||||
### 🎯 Command的特点
|
||||
|
||||
- 🎯 **确定性执行**:匹配到命令立即执行,无随机性
|
||||
- ⚡ **即时响应**:用户主动触发,快速响应
|
||||
- 🔍 **正则匹配**:通过正则表达式精确匹配用户输入
|
||||
- 🛑 **拦截控制**:可以控制是否阻止消息继续处理
|
||||
- 📝 **参数解析**:支持从用户输入中提取参数
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Command组件的基本结构
|
||||
|
||||
首先,Command组件需要继承自`BaseCommand`类,并实现必要的方法。
|
||||
|
||||
```python
|
||||
class ExampleCommand(BaseCommand):
|
||||
command_name = "example" # 命令名称,作为唯一标识符
|
||||
command_description = "这是一个示例命令" # 命令描述
|
||||
command_pattern = r"" # 命令匹配的正则表达式
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str], bool]:
|
||||
"""
|
||||
执行Command的主要逻辑
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str, bool]:
|
||||
- 第一个bool表示是否成功执行
|
||||
- 第二个str是执行结果消息
|
||||
- 第三个bool表示是否需要阻止消息继续处理
|
||||
"""
|
||||
# ---- 执行命令的逻辑 ----
|
||||
return True, "执行成功", False
|
||||
```
|
||||
**`command_pattern`**: 该Command匹配的正则表达式,用于精确匹配用户输入。
|
||||
|
||||
请注意:如果希望能获取到命令中的参数,请在正则表达式中使用有命名的捕获组,例如`(?P<param_name>pattern)`。
|
||||
|
||||
这样在匹配时,内部实现可以使用`re.match.groupdict()`方法获取到所有捕获组的参数,并以字典的形式存储在`self.matched_groups`中。
|
||||
|
||||
### 匹配样例
|
||||
假设我们有一个命令`/example param1=value1 param2=value2`,对应的正则表达式可以是:
|
||||
|
||||
```python
|
||||
class ExampleCommand(BaseCommand):
|
||||
command_name = "example"
|
||||
command_description = "这是一个示例命令"
|
||||
command_pattern = r"/example (?P<param1>\w+) (?P<param2>\w+)"
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str], bool]:
|
||||
# 获取匹配的参数
|
||||
param1 = self.matched_groups.get("param1")
|
||||
param2 = self.matched_groups.get("param2")
|
||||
|
||||
# 执行逻辑
|
||||
return True, f"参数1: {param1}, 参数2: {param2}", False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command 内置方法说明
|
||||
```python
|
||||
class BaseCommand:
|
||||
def get_config(self, key: str, default=None):
|
||||
"""获取插件配置值,使用嵌套键访问"""
|
||||
|
||||
async def send_text(self, content: str, reply_to: str = "") -> bool:
|
||||
"""发送回复消息"""
|
||||
|
||||
async def send_type(self, message_type: str, content: str, display_message: str = "", typing: bool = False, reply_to: str = "") -> bool:
|
||||
"""发送指定类型的回复消息到当前聊天环境"""
|
||||
|
||||
async def send_command(self, command_name: str, args: Optional[dict] = None, display_message: str = "", storage_message: bool = True) -> bool:
|
||||
"""发送命令消息"""
|
||||
|
||||
async def send_emoji(self, emoji_base64: str) -> bool:
|
||||
"""发送表情包"""
|
||||
|
||||
async def send_image(self, image_base64: str) -> bool:
|
||||
"""发送图片"""
|
||||
```
|
||||
具体参数与用法参见`BaseCommand`基类的定义。
|
||||
@@ -1,347 +0,0 @@
|
||||
# ⚙️ 插件配置完整指南
|
||||
|
||||
本文档将全面指导你如何为你的插件**定义配置**和在组件中**访问配置**,帮助你构建一个健壮、规范且自带文档的配置系统。
|
||||
|
||||
> **🚨 重要原则:任何时候都不要手动创建 config.toml 文件!**
|
||||
>
|
||||
> 系统会根据你在代码中定义的 `config_schema` 自动生成配置文件。手动创建配置文件会破坏自动化流程,导致配置不一致、缺失注释和文档等问题。
|
||||
|
||||
## 配置版本管理
|
||||
|
||||
### 🎯 版本管理概述
|
||||
|
||||
插件系统提供了强大的**配置版本管理机制**,可以在插件升级时自动处理配置文件的迁移和更新,确保配置结构始终与代码保持同步。
|
||||
|
||||
### 🔄 配置版本管理工作流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[插件加载] --> B[检查配置文件]
|
||||
B --> C{配置文件存在?}
|
||||
C -->|不存在| D[生成默认配置]
|
||||
C -->|存在| E[读取当前版本]
|
||||
E --> F{有版本信息?}
|
||||
F -->|无版本| G[跳过版本检查<br/>直接加载配置]
|
||||
F -->|有版本| H{版本匹配?}
|
||||
H -->|匹配| I[直接加载配置]
|
||||
H -->|不匹配| J[配置迁移]
|
||||
J --> K[生成新配置结构]
|
||||
K --> L[迁移旧配置值]
|
||||
L --> M[保存迁移后配置]
|
||||
M --> N[配置加载完成]
|
||||
D --> N
|
||||
G --> N
|
||||
I --> N
|
||||
|
||||
style J fill:#FFB6C1
|
||||
style K fill:#90EE90
|
||||
style G fill:#87CEEB
|
||||
style N fill:#DDA0DD
|
||||
```
|
||||
|
||||
### 📊 版本管理策略
|
||||
|
||||
#### 1. 配置版本定义
|
||||
|
||||
在 `config_schema` 的 `plugin` 节中定义 `config_version`:
|
||||
|
||||
```python
|
||||
config_schema = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="1.2.0", description="配置文件版本"),
|
||||
},
|
||||
# 其他配置...
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 版本检查行为
|
||||
|
||||
- **无版本信息** (`config_version` 不存在)
|
||||
- 系统会**跳过版本检查**,直接加载现有配置
|
||||
- 适用于旧版本插件的兼容性处理
|
||||
- 日志显示:`配置文件无版本信息,跳过版本检查`
|
||||
|
||||
- **有版本信息** (存在 `config_version` 字段)
|
||||
- 比较当前版本与期望版本
|
||||
- 版本不匹配时自动执行配置迁移
|
||||
- 版本匹配时直接加载配置
|
||||
|
||||
#### 3. 配置迁移过程
|
||||
|
||||
当检测到版本不匹配时,系统会:
|
||||
|
||||
1. **生成新配置结构** - 根据最新的 `config_schema` 生成新的配置结构
|
||||
2. **迁移配置值** - 将旧配置文件中的值迁移到新结构中
|
||||
3. **处理新增字段** - 新增的配置项使用默认值
|
||||
4. **更新版本号** - `config_version` 字段自动更新为最新版本
|
||||
5. **保存配置文件** - 迁移后的配置直接覆盖原文件**(不保留备份)**
|
||||
|
||||
### 🔧 实际使用示例
|
||||
|
||||
#### 版本升级场景
|
||||
|
||||
假设你的插件从 v1.0 升级到 v1.1,新增了权限管理功能:
|
||||
|
||||
**旧版本配置 (v1.0.0):**
|
||||
```toml
|
||||
[plugin]
|
||||
enabled = true
|
||||
config_version = "1.0.0"
|
||||
|
||||
[mute]
|
||||
min_duration = 60
|
||||
max_duration = 3600
|
||||
```
|
||||
|
||||
**新版本Schema (v1.1.0):**
|
||||
```python
|
||||
config_schema = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||
"config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"),
|
||||
},
|
||||
"mute": {
|
||||
"min_duration": ConfigField(type=int, default=60, description="最短禁言时长(秒)"),
|
||||
"max_duration": ConfigField(type=int, default=2592000, description="最长禁言时长(秒)"),
|
||||
},
|
||||
"permissions": { # 新增的配置节
|
||||
"allowed_users": ConfigField(type=list, default=[], description="允许的用户列表"),
|
||||
"allowed_groups": ConfigField(type=list, default=[], description="允许的群组列表"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**迁移后配置 (v1.1.0):**
|
||||
```toml
|
||||
[plugin]
|
||||
enabled = true # 保留原值
|
||||
config_version = "1.1.0" # 自动更新
|
||||
|
||||
[mute]
|
||||
min_duration = 60 # 保留原值
|
||||
max_duration = 3600 # 保留原值
|
||||
|
||||
[permissions] # 新增节,使用默认值
|
||||
allowed_users = []
|
||||
allowed_groups = []
|
||||
```
|
||||
|
||||
#### 无版本配置的兼容性
|
||||
|
||||
对于没有版本信息的旧配置文件:
|
||||
|
||||
**旧配置文件(无版本):**
|
||||
```toml
|
||||
[plugin]
|
||||
enabled = true
|
||||
# 没有 config_version 字段
|
||||
|
||||
[mute]
|
||||
min_duration = 120
|
||||
```
|
||||
|
||||
**系统行为:**
|
||||
- 检测到无版本信息
|
||||
- 跳过版本检查和迁移
|
||||
- 直接加载现有配置
|
||||
- 新增的配置项在代码中使用默认值访问
|
||||
- 系统会详细记录配置迁移过程。
|
||||
|
||||
### ⚠️ 重要注意事项
|
||||
|
||||
#### 1. 版本号管理
|
||||
- 当你修改 `config_schema` 时,**必须同步更新** `config_version`
|
||||
- 请使用语义化版本号 (例如:`1.0.0`, `1.1.0`, `2.0.0`)
|
||||
|
||||
#### 2. 迁移策略
|
||||
- **保留原值优先**: 迁移时优先保留用户的原有配置值
|
||||
- **新增字段默认值**: 新增的配置项使用Schema中定义的默认值
|
||||
- **移除字段警告**: 如果某个配置项在新版本中被移除,会在日志中显示警告
|
||||
|
||||
#### 3. 兼容性考虑
|
||||
- **旧版本兼容**: 无版本信息的配置文件会跳过版本检查
|
||||
- **不保留备份**: 迁移后直接覆盖原配置文件,不保留备份
|
||||
- **失败安全**: 如果迁移过程中出现错误,会回退到原配置
|
||||
|
||||
## 配置定义
|
||||
|
||||
配置的定义在你的插件主类(继承自 `BasePlugin`)中完成,主要通过两个类属性:
|
||||
|
||||
1. `config_section_descriptions`: 一个字典,用于描述配置文件的各个区段(`[section]`)。
|
||||
2. `config_schema`: 核心部分,一个嵌套字典,用于定义每个区段下的具体配置项。
|
||||
|
||||
### `ConfigField`:配置项的基石
|
||||
|
||||
每个配置项都通过一个 `ConfigField` 对象来定义。
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from src.plugin_system.base.config_types import ConfigField
|
||||
|
||||
@dataclass
|
||||
class ConfigField:
|
||||
"""配置字段定义"""
|
||||
type: type # 字段类型 (例如 str, int, float, bool, list)
|
||||
default: Any # 默认值
|
||||
description: str # 字段描述 (将作为注释生成到配置文件中)
|
||||
example: Optional[str] = None # 示例值 (可选)
|
||||
required: bool = False # 是否必需 (可选, 主要用于文档提示)
|
||||
choices: Optional[List[Any]] = None # 可选值列表 (可选)
|
||||
```
|
||||
|
||||
### 配置示例
|
||||
|
||||
让我们以一个功能丰富的 `MutePlugin` 为例,看看如何定义它的配置。
|
||||
|
||||
```python
|
||||
# src/plugins/built_in/mute_plugin/plugin.py
|
||||
|
||||
from src.plugin_system import BasePlugin, register_plugin, ConfigField
|
||||
from typing import List, Tuple, Type
|
||||
|
||||
@register_plugin
|
||||
class MutePlugin(BasePlugin):
|
||||
"""禁言插件"""
|
||||
|
||||
# 这里是插件基本信息,略去
|
||||
|
||||
# 步骤1: 定义配置节的描述
|
||||
config_section_descriptions = {
|
||||
"plugin": "插件启用配置",
|
||||
"components": "组件启用控制",
|
||||
"mute": "核心禁言功能配置",
|
||||
"smart_mute": "智能禁言Action的专属配置",
|
||||
"logging": "日志记录相关配置"
|
||||
}
|
||||
|
||||
# 步骤2: 使用ConfigField定义详细的配置Schema
|
||||
config_schema = {
|
||||
"plugin": {
|
||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件")
|
||||
},
|
||||
"components": {
|
||||
"enable_smart_mute": ConfigField(type=bool, default=True, description="是否启用智能禁言Action"),
|
||||
"enable_mute_command": ConfigField(type=bool, default=False, description="是否启用禁言命令Command")
|
||||
},
|
||||
"mute": {
|
||||
"min_duration": ConfigField(type=int, default=60, description="最短禁言时长(秒)"),
|
||||
"max_duration": ConfigField(type=int, default=2592000, description="最长禁言时长(秒),默认30天"),
|
||||
"templates": ConfigField(
|
||||
type=list,
|
||||
default=["好的,禁言 {target} {duration},理由:{reason}", "收到,对 {target} 执行禁言 {duration}"],
|
||||
description="成功禁言后发送的随机消息模板"
|
||||
)
|
||||
},
|
||||
"smart_mute": {
|
||||
"keyword_sensitivity": ConfigField(
|
||||
type=str,
|
||||
default="normal",
|
||||
description="关键词激活的敏感度",
|
||||
choices=["low", "normal", "high"] # 定义可选值
|
||||
),
|
||||
},
|
||||
"logging": {
|
||||
"level": ConfigField(
|
||||
type=str,
|
||||
default="INFO",
|
||||
description="日志记录级别",
|
||||
choices=["DEBUG", "INFO", "WARNING", "ERROR"]
|
||||
),
|
||||
"prefix": ConfigField(type=str, default="[MutePlugin]", description="日志记录前缀", example="[MyMutePlugin]")
|
||||
}
|
||||
}
|
||||
|
||||
# 这里是插件方法,略去
|
||||
```
|
||||
|
||||
当 `mute_plugin` 首次加载且其目录中不存在 `config.toml` 时,系统会自动创建以下文件:
|
||||
|
||||
```toml
|
||||
# mute_plugin - 自动生成的配置文件
|
||||
# 群聊禁言管理插件,提供智能禁言功能
|
||||
|
||||
# 插件启用配置
|
||||
[plugin]
|
||||
|
||||
# 是否启用插件
|
||||
enabled = false
|
||||
|
||||
|
||||
# 组件启用控制
|
||||
[components]
|
||||
|
||||
# 是否启用智能禁言Action
|
||||
enable_smart_mute = true
|
||||
|
||||
# 是否启用禁言命令Command
|
||||
enable_mute_command = false
|
||||
|
||||
|
||||
# 核心禁言功能配置
|
||||
[mute]
|
||||
|
||||
# 最短禁言时长(秒)
|
||||
min_duration = 60
|
||||
|
||||
# 最长禁言时长(秒),默认30天
|
||||
max_duration = 2592000
|
||||
|
||||
# 成功禁言后发送的随机消息模板
|
||||
templates = ["好的,禁言 {target} {duration},理由:{reason}", "收到,对 {target} 执行禁言 {duration}"]
|
||||
|
||||
|
||||
# 智能禁言Action的专属配置
|
||||
[smart_mute]
|
||||
|
||||
# 关键词激活的敏感度
|
||||
# 可选值: low, normal, high
|
||||
keyword_sensitivity = "normal"
|
||||
|
||||
|
||||
# 日志记录相关配置
|
||||
[logging]
|
||||
|
||||
# 日志记录级别
|
||||
# 可选值: DEBUG, INFO, WARNING, ERROR
|
||||
level = "INFO"
|
||||
|
||||
# 日志记录前缀
|
||||
# 示例: [MyMutePlugin]
|
||||
prefix = "[MutePlugin]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置访问
|
||||
|
||||
如果你想要在你的组件中访问配置,可以通过组件内置的 `get_config()` 方法访问配置。
|
||||
|
||||
其参数为一个命名空间化的字符串。以上面的 `MutePlugin` 为例,你可以这样访问配置:
|
||||
|
||||
```python
|
||||
enable_smart_mute = self.get_config("components.enable_smart_mute", True)
|
||||
```
|
||||
|
||||
如果尝试访问了一个不存在的配置项,系统会自动返回默认值(你传递的)或者 `None`。
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践与注意事项
|
||||
|
||||
|
||||
**🚨 核心原则:永远不要手动创建 config.toml 文件!**
|
||||
|
||||
1. **🔥 绝不手动创建配置文件**: **任何时候都不要手动创建 `config.toml` 文件**!必须通过在 `plugin.py` 中定义 `config_schema` 让系统自动生成。
|
||||
- ❌ **禁止**:`touch config.toml`、手动编写配置文件
|
||||
- ✅ **正确**:定义 `config_schema`,启动插件,让系统自动生成
|
||||
|
||||
2. **Schema优先**: 所有配置项都必须在 `config_schema` 中声明,包括类型、默认值和描述。
|
||||
|
||||
3. **描述清晰**: 为每个 `ConfigField` 和 `config_section_descriptions` 编写清晰、准确的描述。这会直接成为你的插件文档的一部分。
|
||||
|
||||
4. **提供合理默认值**: 确保你的插件在默认配置下就能正常运行(或处于一个安全禁用的状态)。
|
||||
|
||||
5. **gitignore**: 将 `plugins/*/config.toml` 或 `src/plugins/built_in/*/config.toml` 加入 `.gitignore`,以避免提交个人敏感信息。
|
||||
|
||||
6. **配置文件只供修改**: 自动生成的 `config.toml` 文件只应该被用户**修改**,而不是从零创建。
|
||||
@@ -1,40 +0,0 @@
|
||||
# 📦 插件依赖管理系统
|
||||
|
||||
现在的Python依赖包管理依然存在问题,请保留你的`python_dependencies`属性,等待后续重构。
|
||||
|
||||
## 📚 详细教程
|
||||
|
||||
### PythonDependency 类详解
|
||||
|
||||
`PythonDependency`是依赖声明的核心类:
|
||||
|
||||
```python
|
||||
PythonDependency(
|
||||
package_name="PIL", # 导入时的包名
|
||||
version=">=11.2.0", # 版本要求
|
||||
optional=False, # 是否为可选依赖
|
||||
description="图像处理库", # 依赖描述
|
||||
install_name="pillow" # pip安装时的包名(可选)
|
||||
)
|
||||
```
|
||||
|
||||
#### 参数说明
|
||||
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `package_name` | str | ✅ | Python导入时使用的包名(如`requests`) |
|
||||
| `version` | str | ❌ | 版本要求,使用pip格式(如`>=1.0.0`, `==2.1.3`) |
|
||||
| `optional` | bool | ❌ | 是否为可选依赖,默认`False` |
|
||||
| `description` | str | ❌ | 依赖的用途描述 |
|
||||
| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同,用于处理安装名称和导入名称不一致的情况 |
|
||||
|
||||
#### 版本格式示例
|
||||
|
||||
```python
|
||||
# 常用版本格式
|
||||
PythonDependency("requests", ">=2.25.0") # 最小版本
|
||||
PythonDependency("numpy", ">=1.20.0,<2.0.0") # 版本范围
|
||||
PythonDependency("pillow", "==8.3.2") # 精确版本
|
||||
PythonDependency("scipy", ">=1.7.0,!=1.8.0") # 排除特定版本
|
||||
```
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,81 +0,0 @@
|
||||
# MaiBot插件开发文档
|
||||
|
||||
> 欢迎来到MaiBot插件系统开发文档!这里是你开始插件开发旅程的最佳起点。
|
||||
|
||||
## 新手入门
|
||||
|
||||
- [📖 快速开始指南](quick-start.md) - 快速创建你的第一个插件
|
||||
|
||||
## 组件功能详解
|
||||
|
||||
- [🧱 Action组件详解](action-components.md) - 掌握最核心的Action组件
|
||||
- [💻 Command组件详解](command-components.md) - 学习直接响应命令的组件
|
||||
- [🔧 Tool组件详解](tool-components.md) - 了解如何扩展信息获取能力
|
||||
- [⚙️ 配置文件系统指南](configuration-guide.md) - 学会使用自动生成的插件配置文件
|
||||
- [📄 Manifest系统指南](manifest-guide.md) - 了解插件元数据管理和配置架构
|
||||
|
||||
Command vs Action 选择指南
|
||||
|
||||
1. 使用Command的场景
|
||||
|
||||
- ✅ 用户需要明确调用特定功能
|
||||
- ✅ 需要精确的参数控制
|
||||
- ✅ 管理和配置操作
|
||||
- ✅ 查询和信息显示
|
||||
- ✅ 系统维护命令
|
||||
|
||||
2. 使用Action的场景
|
||||
|
||||
- ✅ 增强麦麦的智能行为
|
||||
- ✅ 根据上下文自动触发
|
||||
- ✅ 情绪和表情表达
|
||||
- ✅ 智能建议和帮助
|
||||
- ✅ 随机化的互动
|
||||
|
||||
|
||||
## API浏览
|
||||
|
||||
### 消息发送与处理API
|
||||
- [📤 发送API](api/send-api.md) - 各种类型消息发送接口
|
||||
- [消息API](api/message-api.md) - 消息获取,消息构建,消息查询接口
|
||||
- [聊天流API](api/chat-api.md) - 聊天流管理和查询接口
|
||||
|
||||
### AI与生成API
|
||||
- [LLM API](api/llm-api.md) - 大语言模型交互接口,可以使用内置LLM生成内容
|
||||
- [✨ 回复生成器API](api/generator-api.md) - 智能回复生成接口,可以使用内置风格化生成器
|
||||
|
||||
### 表情包API
|
||||
- [😊 表情包API](api/emoji-api.md) - 表情包选择和管理接口
|
||||
|
||||
### 关系系统API
|
||||
- [人物信息API](api/person-api.md) - 用户信息,处理麦麦认识的人和关系的接口
|
||||
|
||||
### 数据与配置API
|
||||
- [🗄️ 数据库API](api/database-api.md) - 数据库操作接口
|
||||
- [⚙️ 配置API](api/config-api.md) - 配置读取和用户信息接口
|
||||
|
||||
### 插件和组件管理API
|
||||
- [🔌 插件API](api/plugin-manage-api.md) - 插件加载和管理接口
|
||||
- [🧩 组件API](api/component-manage-api.md) - 组件注册和管理接口
|
||||
|
||||
### 日志API
|
||||
- [📜 日志API](api/logging-api.md) - logger实例获取接口
|
||||
### 工具API
|
||||
- [🔧 工具API](api/tool-api.md) - tool获取接口
|
||||
|
||||
|
||||
|
||||
## 支持
|
||||
|
||||
> 如果你在文档中发现错误或需要补充,请:
|
||||
|
||||
1. 检查最新的文档版本
|
||||
2. 查看相关示例代码
|
||||
3. 参考其他类似插件
|
||||
4. 提交文档仓库issue
|
||||
|
||||
## 一个方便的小设计
|
||||
|
||||
我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。
|
||||
这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。
|
||||
或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。
|
||||
@@ -1,205 +0,0 @@
|
||||
# 📄 插件Manifest系统指南
|
||||
|
||||
## 概述
|
||||
|
||||
MaiBot插件系统现在强制要求每个插件都必须包含一个 `_manifest.json` 文件。这个文件描述了插件的基本信息、依赖关系、组件等重要元数据。
|
||||
|
||||
### 🔄 配置架构:Manifest与Config的职责分离
|
||||
|
||||
为了避免信息重复和提高维护性,我们采用了**双文件架构**:
|
||||
|
||||
- **`_manifest.json`** - 插件的**静态元数据**
|
||||
- 插件身份信息(名称、版本、描述)
|
||||
- 开发者信息(作者、许可证、仓库)
|
||||
- 系统信息(兼容性、组件列表、分类)
|
||||
|
||||
- **`config.toml`** - 插件的**运行时配置**
|
||||
- 启用状态 (`enabled`)
|
||||
- 功能参数配置
|
||||
- 用户可调整的行为设置
|
||||
|
||||
这种分离确保了:
|
||||
- ✅ 元数据信息统一管理
|
||||
- ✅ 运行时配置灵活调整
|
||||
- ✅ 避免重复维护
|
||||
- ✅ 更清晰的职责划分
|
||||
|
||||
## 🔧 Manifest文件结构
|
||||
|
||||
### 必需字段
|
||||
|
||||
以下字段是必需的,不能为空:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"name": "插件显示名称",
|
||||
"version": "1.0.0",
|
||||
"description": "插件功能描述",
|
||||
"author": {
|
||||
"name": "作者名称"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 可选字段
|
||||
|
||||
以下字段都是可选的,可以根据需要添加:
|
||||
|
||||
```json
|
||||
{
|
||||
"license": "MIT",
|
||||
"host_application": {
|
||||
"min_version": "1.0.0",
|
||||
"max_version": "4.0.0"
|
||||
},
|
||||
"homepage_url": "https://github.com/your-repo",
|
||||
"repository_url": "https://github.com/your-repo",
|
||||
"keywords": ["关键词1", "关键词2"],
|
||||
"categories": ["分类1", "分类2"],
|
||||
"default_locale": "zh-CN",
|
||||
"locales_path": "_locales",
|
||||
"plugin_info": {
|
||||
"is_built_in": false,
|
||||
"plugin_type": "general",
|
||||
"components": [
|
||||
{
|
||||
"type": "action",
|
||||
"name": "组件名称",
|
||||
"description": "组件描述"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠️ 管理工具
|
||||
|
||||
### 使用manifest_tool.py
|
||||
|
||||
我们提供了一个命令行工具来帮助管理manifest文件:
|
||||
|
||||
```bash
|
||||
# 扫描缺少manifest的插件
|
||||
python scripts/manifest_tool.py scan src/plugins
|
||||
|
||||
# 为插件创建最小化manifest文件
|
||||
python scripts/manifest_tool.py create-minimal src/plugins/my_plugin --name "我的插件" --author "作者"
|
||||
|
||||
# 为插件创建完整manifest模板
|
||||
python scripts/manifest_tool.py create-complete src/plugins/my_plugin --name "我的插件"
|
||||
|
||||
# 验证manifest文件
|
||||
python scripts/manifest_tool.py validate src/plugins/my_plugin
|
||||
```
|
||||
|
||||
### 验证示例
|
||||
|
||||
验证通过的示例:
|
||||
```
|
||||
✅ Manifest文件验证通过
|
||||
```
|
||||
|
||||
验证失败的示例:
|
||||
```
|
||||
❌ 验证错误:
|
||||
- 缺少必需字段: name
|
||||
- 作者信息缺少name字段或为空
|
||||
⚠️ 验证警告:
|
||||
- 建议填写字段: license
|
||||
- 建议填写字段: keywords
|
||||
```
|
||||
|
||||
## 🔄 迁移指南
|
||||
|
||||
### 对于现有插件
|
||||
|
||||
1. **检查缺少manifest的插件**:
|
||||
```bash
|
||||
python scripts/manifest_tool.py scan src/plugins
|
||||
```
|
||||
|
||||
2. **为每个插件创建manifest**:
|
||||
```bash
|
||||
python scripts/manifest_tool.py create-minimal src/plugins/your_plugin
|
||||
```
|
||||
|
||||
3. **编辑manifest文件**,填写正确的信息。
|
||||
|
||||
4. **验证manifest**:
|
||||
```bash
|
||||
python scripts/manifest_tool.py validate src/plugins/your_plugin
|
||||
```
|
||||
|
||||
### 对于新插件
|
||||
|
||||
创建新插件时,建议的步骤:
|
||||
|
||||
1. **创建插件目录和基本文件**
|
||||
2. **创建完整manifest模板**:
|
||||
```bash
|
||||
python scripts/manifest_tool.py create-complete src/plugins/new_plugin
|
||||
```
|
||||
3. **根据实际情况修改manifest文件**
|
||||
4. **编写插件代码**
|
||||
5. **验证manifest文件**
|
||||
|
||||
## 📋 字段说明
|
||||
|
||||
### 基本信息
|
||||
- `manifest_version`: manifest格式版本,当前为1
|
||||
- `name`: 插件显示名称(必需)
|
||||
- `version`: 插件版本号(必需)
|
||||
- `description`: 插件功能描述(必需)
|
||||
- `author`: 作者信息(必需)
|
||||
- `name`: 作者名称(必需)
|
||||
- `url`: 作者主页(可选)
|
||||
|
||||
### 许可和URL
|
||||
- `license`: 插件许可证(可选,建议填写)
|
||||
- `homepage_url`: 插件主页(可选)
|
||||
- `repository_url`: 源码仓库地址(可选)
|
||||
|
||||
### 分类和标签
|
||||
- `keywords`: 关键词数组(可选,建议填写)
|
||||
- `categories`: 分类数组(可选,建议填写)
|
||||
|
||||
### 兼容性
|
||||
- `host_application`: 主机应用兼容性(可选,建议填写)
|
||||
- `min_version`: 最低兼容版本
|
||||
- `max_version`: 最高兼容版本
|
||||
|
||||
⚠️ 在不填写的情况下,插件将默认支持所有版本。**(由于我们在不同版本对插件系统进行了大量的重构,这种情况几乎不可能。)**
|
||||
|
||||
### 国际化
|
||||
- `default_locale`: 默认语言(可选)
|
||||
- `locales_path`: 语言文件目录(可选)
|
||||
|
||||
### 插件特定信息
|
||||
- `plugin_info`: 插件详细信息(可选)
|
||||
- `is_built_in`: 是否为内置插件
|
||||
- `plugin_type`: 插件类型
|
||||
- `components`: 组件列表
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **强制要求**:所有插件必须包含`_manifest.json`文件,否则无法加载
|
||||
2. **编码格式**:manifest文件必须使用UTF-8编码
|
||||
3. **JSON格式**:文件必须是有效的JSON格式
|
||||
4. **必需字段**:`manifest_version`、`name`、`version`、`description`、`author.name`是必需的
|
||||
5. **版本兼容**:当前只支持`manifest_version = 1`
|
||||
|
||||
## 🔍 常见问题
|
||||
|
||||
### Q: 可以不填写可选字段吗?
|
||||
A: 可以。所有标记为"可选"的字段都可以不填写,但建议至少填写`license`和`keywords`。
|
||||
|
||||
### Q: manifest验证失败怎么办?
|
||||
A: 根据验证器的错误提示修复相应问题。错误会导致插件加载失败,警告不会。
|
||||
|
||||
## 📚 参考示例
|
||||
|
||||
查看内置插件的manifest文件作为参考:
|
||||
- `src/plugins/built_in/core_actions/_manifest.json`
|
||||
- `src/plugins/built_in/tts_plugin/_manifest.json`
|
||||
- `src/plugins/hello_world_plugin/_manifest.json`
|
||||
@@ -1,428 +0,0 @@
|
||||
# 🚀 快速开始指南
|
||||
|
||||
本指南将带你从零开始创建一个功能完整的MaiCore插件。
|
||||
|
||||
## 📖 概述
|
||||
|
||||
这个指南将带你快速创建你的第一个MaiCore插件。我们将创建一个简单的问候插件,展示插件系统的基本概念。
|
||||
|
||||
以下代码都在我们的`plugins/hello_world_plugin/`目录下。
|
||||
|
||||
### 一个方便的小设计
|
||||
|
||||
在开发中,我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。
|
||||
这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。
|
||||
或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。
|
||||
|
||||
### 📂 准备工作
|
||||
|
||||
确保你已经:
|
||||
|
||||
1. 克隆了MaiCore项目
|
||||
2. 安装了Python依赖
|
||||
3. 了解基本的Python语法
|
||||
|
||||
## 🏗️ 创建插件
|
||||
|
||||
### 1. 创建插件目录
|
||||
|
||||
在项目根目录的 `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": "你的名字"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有关 `_manifest.json` 的详细说明,请参考 [Manifest文件指南](./manifest-guide.md)。
|
||||
|
||||
### 3. 创建最简单的插件
|
||||
|
||||
让我们从最基础的开始!创建 `plugin.py` 文件:
|
||||
|
||||
```python
|
||||
from typing import List, Tuple, Type
|
||||
from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
|
||||
|
||||
@register_plugin # 注册插件
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
# 以下是插件基本信息和方法(必须填写)
|
||||
plugin_name = "hello_world_plugin"
|
||||
enable_plugin = True # 启用插件
|
||||
dependencies = [] # 插件依赖列表(目前为空)
|
||||
python_dependencies = [] # Python依赖列表(目前为空)
|
||||
config_file_name = "config.toml" # 配置文件名
|
||||
config_schema = {} # 配置文件模式(目前为空)
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: # 获取插件组件
|
||||
"""返回插件包含的组件列表(目前是空的)"""
|
||||
return []
|
||||
```
|
||||
|
||||
🎉 恭喜!你刚刚创建了一个最简单但完整的MaiCore插件!
|
||||
|
||||
**解释一下这些代码:**
|
||||
|
||||
- 首先,我们在`plugin.py`中定义了一个HelloWorldPlugin插件类,继承自 `BasePlugin` ,提供基本功能。
|
||||
- 通过给类加上,`@register_plugin` 装饰器,我们告诉系统"这是一个插件"
|
||||
- `plugin_name` 等是插件的基本信息,必须填写
|
||||
- `get_plugin_components()` 返回插件的功能组件,现在我们没有定义任何 Action, Command 或者 EventHandler,所以返回空列表。
|
||||
|
||||
### 4. 测试基础插件
|
||||
|
||||
现在就可以测试这个插件了!启动MaiCore:
|
||||
|
||||
直接通过启动器运行MaiCore或者 `python bot.py`
|
||||
|
||||
在日志中你应该能看到插件被加载的信息。虽然插件还没有任何功能,但它已经成功运行了!
|
||||
|
||||

|
||||
|
||||
### 5. 添加第一个功能:问候Action
|
||||
|
||||
现在我们要给插件加入一个有用的功能,我们从最好玩的Action做起
|
||||
|
||||
Action是一类可以让MaiCore根据自身意愿选择使用的“动作”,在MaiCore中,不论是“回复”还是“不回复”,或者“发送表情”以及“禁言”等等,都是通过Action实现的。
|
||||
|
||||
你可以通过编写动作,来拓展MaiCore的能力,包括发送语音,截图,甚至操作文件,编写代码......
|
||||
|
||||
现在让我们给插件添加第一个简单的功能。这个Action可以对用户发送一句问候语。
|
||||
|
||||
在 `plugin.py` 文件中添加Action组件,完整代码如下:
|
||||
|
||||
```python
|
||||
from typing import List, Tuple, Type
|
||||
from src.plugin_system import (
|
||||
BasePlugin, register_plugin, BaseAction,
|
||||
ComponentInfo, ActionActivationType, ChatMode
|
||||
)
|
||||
|
||||
# ===== Action组件 =====
|
||||
|
||||
class HelloAction(BaseAction):
|
||||
"""问候Action - 简单的问候动作"""
|
||||
|
||||
# === 基本信息(必须填写)===
|
||||
action_name = "hello_greeting"
|
||||
action_description = "向用户发送问候消息"
|
||||
activation_type = ActionActivationType.ALWAYS # 始终激活
|
||||
|
||||
# === 功能描述(必须填写)===
|
||||
action_parameters = {"greeting_message": "要发送的问候消息"}
|
||||
action_require = ["需要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
|
||||
associated_types = ["text"]
|
||||
|
||||
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)
|
||||
|
||||
return True, "发送了问候消息"
|
||||
|
||||
@register_plugin
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name = "hello_world_plugin"
|
||||
enable_plugin = True
|
||||
dependencies = []
|
||||
python_dependencies = []
|
||||
config_file_name = "config.toml"
|
||||
config_schema = {}
|
||||
|
||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||
"""返回插件包含的组件列表"""
|
||||
return [
|
||||
# 添加我们的问候Action
|
||||
(HelloAction.get_action_info(), HelloAction),
|
||||
]
|
||||
```
|
||||
|
||||
**解释一下这些代码:**
|
||||
|
||||
- `HelloAction` 是我们定义的问候动作类,继承自 `BaseAction`,并实现了核心功能。
|
||||
- 在 `HelloWorldPlugin` 中,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `HelloAction` 注册为插件的一个组件。
|
||||
- 这样一来,当插件被加载时,问候动作也会被一并加载,并可以在MaiCore中使用。
|
||||
- `execute()` 函数是Action的核心,定义了当Action被MaiCore选择后,具体要做什么
|
||||
- `self.send_text()` 是发送文本消息的便捷方法
|
||||
|
||||
Action 组件中有关`activation_type`、`action_parameters`、`action_require`、`associated_types` 等的详细说明请参考 [Action组件指南](./action-components.md)。
|
||||
|
||||
### 6. 测试问候Action
|
||||
|
||||
重启MaiCore,然后在聊天中发送任意消息,比如:
|
||||
|
||||
```
|
||||
你好
|
||||
```
|
||||
|
||||
MaiCore可能会选择使用你的问候Action,发送回复:
|
||||
|
||||
```
|
||||
嗨!很开心见到你!😊
|
||||
```
|
||||
|
||||

|
||||
|
||||
> **💡 小提示**:MaiCore会智能地决定什么时候使用它。如果没有立即看到效果,多试几次不同的消息。
|
||||
|
||||
🎉 太棒了!你的插件已经有实际功能了!
|
||||
|
||||
### 7. 添加第二个功能:时间查询Command
|
||||
|
||||
现在让我们添加一个Command组件。Command和Action不同,它是直接响应用户命令的:
|
||||
|
||||
Command是最简单,最直接的响应,不由LLM判断选择使用
|
||||
|
||||
```python
|
||||
# 在现有代码基础上,添加Command组件
|
||||
import datetime
|
||||
from src.plugin_system import BaseCommand
|
||||
#导入Command基类
|
||||
|
||||
class TimeCommand(BaseCommand):
|
||||
"""时间查询Command - 响应/time命令"""
|
||||
|
||||
command_name = "time"
|
||||
command_description = "查询当前时间"
|
||||
|
||||
# === 命令设置(必须填写)===
|
||||
command_pattern = r"^/time$" # 精确匹配 "/time" 命令
|
||||
|
||||
async def execute(self) -> Tuple[bool, Optional[str], bool]:
|
||||
"""执行时间查询"""
|
||||
# 获取当前时间
|
||||
time_format: str = "%Y-%m-%d %H:%M:%S"
|
||||
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
|
||||
|
||||
@register_plugin
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
# 插件基本信息
|
||||
plugin_name = "hello_world_plugin"
|
||||
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),
|
||||
(TimeCommand.get_command_info(), TimeCommand),
|
||||
]
|
||||
```
|
||||
|
||||
同样的,我们通过 `get_plugin_components()` 方法,通过调用`get_action_info()`这个内置方法将 `TimeCommand` 注册为插件的一个组件。
|
||||
|
||||
**Command组件解释:**
|
||||
|
||||
- `command_pattern` 使用正则表达式匹配用户输入
|
||||
- `^/time$` 表示精确匹配 "/time"
|
||||
|
||||
有关 Command 组件的更多信息,请参考 [Command组件指南](./command-components.md)。
|
||||
|
||||
### 8. 测试时间查询Command
|
||||
|
||||
重启MaiCore,发送命令:
|
||||
|
||||
```
|
||||
/time
|
||||
```
|
||||
|
||||
你应该会收到回复:
|
||||
|
||||
```
|
||||
⏰ 当前时间:2024-01-01 12:00:00
|
||||
```
|
||||
|
||||
🎉 太棒了!现在你已经了解了基本的 Action 和 Command 组件的使用方法。你可以根据自己的需求,继续扩展插件的功能,添加更多的 Action 和 Command 组件,让你的插件更加丰富和强大!
|
||||
|
||||
---
|
||||
|
||||
## 进阶教程
|
||||
|
||||
如果你想让插件更加灵活和强大,可以参考接下来的进阶教程。
|
||||
|
||||
### 1. 添加配置文件
|
||||
|
||||
想要为插件添加配置文件吗?让我们一起来配置`config_schema`属性!
|
||||
|
||||
> **🚨 重要:不要手动创建config.toml文件!**
|
||||
>
|
||||
> 我们需要在插件代码中定义配置Schema,让系统自动生成配置文件。
|
||||
|
||||
首先,在插件类中定义配置Schema:
|
||||
|
||||
```python
|
||||
from src.plugin_system import ConfigField
|
||||
|
||||
@register_plugin
|
||||
class HelloWorldPlugin(BasePlugin):
|
||||
"""Hello World插件 - 你的第一个MaiCore插件"""
|
||||
|
||||
# 插件基本信息
|
||||
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: dict = {
|
||||
"plugin": {
|
||||
"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="是否启用表情符号"),
|
||||
},
|
||||
"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),
|
||||
(TimeCommand.get_command_info(), TimeCommand),
|
||||
]
|
||||
```
|
||||
|
||||
这会生成一个如下的 `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
|
||||
class HelloAction(BaseAction):
|
||||
"""问候Action - 简单的问候动作"""
|
||||
|
||||
# === 基本信息(必须填写)===
|
||||
action_name = "hello_greeting"
|
||||
action_description = "向用户发送问候消息"
|
||||
activation_type = ActionActivationType.ALWAYS # 始终激活
|
||||
|
||||
# === 功能描述(必须填写)===
|
||||
action_parameters = {"greeting_message": "要发送的问候消息"}
|
||||
action_require = ["需要发送友好问候时使用", "当有人向你问好时使用", "当你遇见没有见过的人时使用"]
|
||||
associated_types = ["text"]
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
**配置系统工作流程:**
|
||||
|
||||
1. **定义Schema**: 在插件代码中定义配置结构
|
||||
2. **自动生成**: 启动插件时,系统会自动生成 `config.toml` 文件
|
||||
3. **用户修改**: 用户可以修改生成的配置文件
|
||||
4. **代码读取**: 使用 `self.get_config()` 读取配置值
|
||||
|
||||
**绝对不要手动创建 `config.toml` 文件!**
|
||||
|
||||
更详细的配置系统介绍请参考 [配置指南](./configuration-guide.md)。
|
||||
|
||||
### 2. 创建说明文档
|
||||
|
||||
你可以创建一个 `README.md` 文件,描述插件的功能和使用方法。
|
||||
|
||||
### 3. 发布到插件市场
|
||||
|
||||
如果你想让更多人使用你的插件,可以将它发布到MaiCore的插件市场。
|
||||
|
||||
这部分请参考 [plugin-repo](https://github.com/Maim-with-u/plugin-repo) 的文档。
|
||||
|
||||
---
|
||||
|
||||
🎉 恭喜你!你已经成功的创建了自己的插件了!
|
||||
@@ -1,246 +0,0 @@
|
||||
# 🔧 工具组件详解
|
||||
|
||||
## 📖 什么是工具
|
||||
|
||||
工具是MaiBot的信息获取能力扩展组件。如果说Action组件功能五花八门,可以拓展麦麦能做的事情,那么Tool就是在某个过程中拓宽了麦麦能够获得的信息量。
|
||||
|
||||
### 🎯 工具的特点
|
||||
|
||||
- 🔍 **信息获取增强**:扩展麦麦获取外部信息的能力
|
||||
- 📊 **数据丰富**:帮助麦麦获得更多背景信息和实时数据
|
||||
- 🔌 **插件式架构**:支持独立开发和注册新工具
|
||||
- ⚡ **自动发现**:工具会被系统自动识别和注册
|
||||
|
||||
### 🆚 Tool vs Action vs Command 区别
|
||||
|
||||
| 特征 | Action | Command | Tool |
|
||||
|-----|-------|---------|------|
|
||||
| **主要用途** | 扩展麦麦行为能力 | 响应用户指令 | 扩展麦麦信息获取 |
|
||||
| **触发方式** | 麦麦智能决策 | 用户主动触发 | LLM根据需要调用 |
|
||||
| **目标** | 让麦麦做更多事情 | 提供具体功能 | 让麦麦知道更多信息 |
|
||||
| **使用场景** | 增强交互体验 | 功能服务 | 信息查询和分析 |
|
||||
|
||||
## 🏗️ Tool组件的基本结构
|
||||
|
||||
每个工具必须继承 `BaseTool` 基类并实现以下属性和方法:
|
||||
```python
|
||||
from src.plugin_system import BaseTool, ToolParamType
|
||||
|
||||
class MyTool(BaseTool):
|
||||
# 工具名称,必须唯一
|
||||
name = "my_tool"
|
||||
|
||||
# 工具描述,告诉LLM这个工具的用途
|
||||
description = "这个工具用于获取特定类型的信息"
|
||||
|
||||
# 参数定义,仅定义参数
|
||||
# 比如想要定义一个类似下面的openai格式的参数表,则可以这么定义:
|
||||
# {
|
||||
# "type": "object",
|
||||
# "properties": {
|
||||
# "query": {
|
||||
# "type": "string",
|
||||
# "description": "查询参数"
|
||||
# },
|
||||
# "limit": {
|
||||
# "type": "integer",
|
||||
# "description": "结果数量限制"
|
||||
# "enum": [10, 20, 50] # 可选值
|
||||
# }
|
||||
# },
|
||||
# "required": ["query"]
|
||||
# }
|
||||
parameters = [
|
||||
("query", ToolParamType.STRING, "查询参数", True, None), # 必填参数
|
||||
("limit", ToolParamType.INTEGER, "结果数量限制", False, ["10", "20", "50"]) # 可选参数
|
||||
]
|
||||
|
||||
available_for_llm = True # 是否对LLM可用
|
||||
|
||||
async def execute(self, function_args: Dict[str, Any]):
|
||||
"""执行工具逻辑"""
|
||||
# 实现工具功能
|
||||
result = f"查询结果: {function_args.get('query')}"
|
||||
|
||||
return {
|
||||
"name": self.name,
|
||||
"content": result
|
||||
}
|
||||
```
|
||||
|
||||
### 属性说明
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|-----|------|------|
|
||||
| `name` | str | 工具的唯一标识名称 |
|
||||
| `description` | str | 工具功能描述,帮助LLM理解用途 |
|
||||
| `parameters` | list[tuple] | 参数定义 |
|
||||
|
||||
其构造而成的工具定义为:
|
||||
```python
|
||||
definition: Dict[str, Any] = {"name": cls.name, "description": cls.description, "parameters": cls.parameters}
|
||||
```
|
||||
|
||||
### 方法说明
|
||||
|
||||
| 方法 | 参数 | 返回值 | 说明 |
|
||||
|-----|------|--------|------|
|
||||
| `execute` | `function_args` | `dict` | 执行工具核心逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 完整工具示例
|
||||
|
||||
完成一个天气查询工具
|
||||
|
||||
```python
|
||||
from src.plugin_system import BaseTool
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
class WeatherTool(BaseTool):
|
||||
"""天气查询工具 - 获取指定城市的实时天气信息"""
|
||||
|
||||
name = "weather_query"
|
||||
description = "查询指定城市的实时天气信息,包括温度、湿度、天气状况等"
|
||||
available_for_llm = True # 允许LLM调用此工具
|
||||
parameters = [
|
||||
("city", ToolParamType.STRING, "要查询天气的城市名称,如:北京、上海、纽约", True, None),
|
||||
("country", ToolParamType.STRING, "国家代码,如:CN、US,可选参数", False, None)
|
||||
]
|
||||
|
||||
async def execute(self, function_args: dict):
|
||||
"""执行天气查询"""
|
||||
try:
|
||||
city = function_args.get("city")
|
||||
country = function_args.get("country", "")
|
||||
|
||||
# 构建查询参数
|
||||
location = f"{city},{country}" if country else city
|
||||
|
||||
# 调用天气API(示例)
|
||||
weather_data = await self._fetch_weather(location)
|
||||
|
||||
# 格式化结果
|
||||
result = self._format_weather_data(weather_data)
|
||||
|
||||
return {
|
||||
"name": self.name,
|
||||
"content": result
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"name": self.name,
|
||||
"content": f"天气查询失败: {str(e)}"
|
||||
}
|
||||
|
||||
async def _fetch_weather(self, location: str) -> dict:
|
||||
"""获取天气数据"""
|
||||
# 这里是示例,实际需要接入真实的天气API
|
||||
api_url = f"http://api.weather.com/v1/current?q={location}"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(api_url) as response:
|
||||
return await response.json()
|
||||
|
||||
def _format_weather_data(self, data: dict) -> str:
|
||||
"""格式化天气数据"""
|
||||
if not data:
|
||||
return "暂无天气数据"
|
||||
|
||||
# 提取关键信息
|
||||
city = data.get("location", {}).get("name", "未知城市")
|
||||
temp = data.get("current", {}).get("temp_c", "未知")
|
||||
condition = data.get("current", {}).get("condition", {}).get("text", "未知")
|
||||
humidity = data.get("current", {}).get("humidity", "未知")
|
||||
|
||||
# 格式化输出
|
||||
return f"""
|
||||
🌤️ {city} 实时天气
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
🌡️ 温度: {temp}°C
|
||||
☁️ 天气: {condition}
|
||||
💧 湿度: {humidity}%
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
""".strip()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 注意事项和限制
|
||||
|
||||
### 当前限制
|
||||
|
||||
1. **适用范围**:主要适用于信息获取场景
|
||||
2. **配置要求**:必须开启工具处理器
|
||||
|
||||
### 开发建议
|
||||
|
||||
1. **功能专一**:每个工具专注单一功能
|
||||
2. **参数明确**:清晰定义工具参数和用途
|
||||
3. **错误处理**:完善的异常处理和错误反馈
|
||||
4. **性能考虑**:避免长时间阻塞操作
|
||||
5. **信息准确**:确保获取信息的准确性和时效性
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
### 1. 工具命名规范
|
||||
#### ✅ 好的命名
|
||||
```python
|
||||
name = "weather_query" # 清晰表达功能
|
||||
name = "knowledge_search" # 描述性强
|
||||
name = "stock_price_check" # 功能明确
|
||||
```
|
||||
#### ❌ 避免的命名
|
||||
```python
|
||||
name = "tool1" # 无意义
|
||||
name = "wq" # 过于简短
|
||||
name = "weather_and_news" # 功能过于复杂
|
||||
```
|
||||
|
||||
### 2. 描述规范
|
||||
#### ✅ 良好的描述
|
||||
```python
|
||||
description = "查询指定城市的实时天气信息,包括温度、湿度、天气状况"
|
||||
```
|
||||
#### ❌ 避免的描述
|
||||
```python
|
||||
description = "天气" # 过于简单
|
||||
description = "获取信息" # 不够具体
|
||||
```
|
||||
|
||||
### 3. 参数设计
|
||||
|
||||
#### ✅ 合理的参数设计
|
||||
```python
|
||||
parameters = [
|
||||
("city", ToolParamType.STRING, "城市名称,如:北京、上海", True, None),
|
||||
("unit", ToolParamType.STRING, "温度单位:celsius 或 fahrenheit", False, ["celsius", "fahrenheit"])
|
||||
]
|
||||
```
|
||||
#### ❌ 避免的参数设计
|
||||
```python
|
||||
parameters = [
|
||||
("data", "string", "数据", True) # 参数过于模糊
|
||||
]
|
||||
```
|
||||
|
||||
### 4. 结果格式化
|
||||
#### ✅ 良好的结果格式
|
||||
```python
|
||||
def _format_result(self, data):
|
||||
return f"""
|
||||
🔍 查询结果
|
||||
━━━━━━━━━━━━
|
||||
📊 数据: {data['value']}
|
||||
📅 时间: {data['timestamp']}
|
||||
📝 说明: {data['description']}
|
||||
━━━━━━━━━━━━
|
||||
""".strip()
|
||||
```
|
||||
#### ❌ 避免的结果格式
|
||||
```python
|
||||
def _format_result(self, data):
|
||||
return str(data) # 直接返回原始数据
|
||||
```
|
||||
14
saka.py
14
saka.py
@@ -10,21 +10,19 @@ MaiSaka - 程序入口
|
||||
ENABLE_THINKING - 是否启用思考模式 (可选, true/false, 不设置则不发送该参数)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录和 src/maisaka 到 Python 路径
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
# 添加项目根目录到 Python 路径
|
||||
_root = Path(__file__).parent
|
||||
_maisaka_path = _root / "src" / "maisaka"
|
||||
if str(_root) not in sys.path:
|
||||
sys.path.insert(0, str(_root))
|
||||
if str(_maisaka_path) not in sys.path:
|
||||
sys.path.insert(0, str(_maisaka_path))
|
||||
|
||||
from src.cli.console import console # noqa: E402
|
||||
from src.cli.maisaka_cli import BufferCLI # noqa: E402
|
||||
from src.prompt.prompt_manager import prompt_manager # noqa: E402
|
||||
from src.maisaka.cli import BufferCLI # noqa: E402
|
||||
from src.maisaka.config import console # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -14,7 +14,6 @@ from src.common.database.database import get_db_session
|
||||
from src.common.database.database_model import Images, ImageType
|
||||
from src.common.data_models.image_data_model import MaiImage
|
||||
from src.config.config import global_config
|
||||
from src.common.data_models.llm_service_data_models import LLMImageOptions
|
||||
from src.services.llm_service import LLMServiceClient
|
||||
|
||||
install(extra_lines=3)
|
||||
@@ -382,7 +381,6 @@ class ImageManager:
|
||||
prompt,
|
||||
image_base64,
|
||||
image_format,
|
||||
options=LLMImageOptions(temperature=0.4),
|
||||
)
|
||||
description = generation_result.response
|
||||
if not description:
|
||||
|
||||
@@ -170,6 +170,38 @@ def _migrate_expression_learning_list(expr: dict[str, Any]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _migrate_chat_talk_value_rules(chat: dict[str, Any]) -> bool:
|
||||
"""
|
||||
将旧版 target 字段迁移为当前运行时使用的 platform/item_id/rule_type 结构。
|
||||
"""
|
||||
talk_value_rules = _as_list(chat.get("talk_value_rules"))
|
||||
if talk_value_rules is None:
|
||||
return False
|
||||
|
||||
migrated = False
|
||||
for rule in talk_value_rules:
|
||||
rule_item = _as_dict(rule)
|
||||
if rule_item is None or "target" not in rule_item:
|
||||
continue
|
||||
|
||||
target_raw = rule_item.get("target")
|
||||
target = "" if target_raw is None else str(target_raw).strip()
|
||||
if not target:
|
||||
parsed = {"platform": "", "item_id": "", "rule_type": "group"}
|
||||
else:
|
||||
parsed = _parse_triplet_target(target)
|
||||
if parsed is None:
|
||||
continue
|
||||
|
||||
rule_item["platform"] = parsed["platform"]
|
||||
rule_item["item_id"] = parsed["item_id"]
|
||||
rule_item["rule_type"] = parsed["rule_type"]
|
||||
rule_item.pop("target", None)
|
||||
migrated = True
|
||||
|
||||
return migrated
|
||||
|
||||
|
||||
def _migrate_expression_groups(expr: dict[str, Any]) -> bool:
|
||||
"""
|
||||
将旧版 expression.expression_groups 转成当前结构。
|
||||
@@ -316,6 +348,11 @@ def try_migrate_legacy_bot_config_dict(data: dict[str, Any]) -> MigrationResult:
|
||||
migrated_any = True
|
||||
reasons.append("bot.qq_account_empty")
|
||||
|
||||
chat = _as_dict(data.get("chat"))
|
||||
if chat is not None and _migrate_chat_talk_value_rules(chat):
|
||||
migrated_any = True
|
||||
reasons.append("chat.talk_value_rules_target")
|
||||
|
||||
expr = _as_dict(data.get("expression"))
|
||||
if expr is not None:
|
||||
if _migrate_expression_learning_list(expr):
|
||||
|
||||
@@ -14,7 +14,7 @@ from sqlmodel import select
|
||||
import Levenshtein
|
||||
|
||||
from src.common.data_models.image_data_model import MaiEmoji
|
||||
from src.common.data_models.llm_service_data_models import LLMGenerationOptions, LLMImageOptions
|
||||
from src.common.data_models.llm_service_data_models import LLMGenerationOptions
|
||||
from src.common.database.database import get_db_session, get_db_session_manual
|
||||
from src.common.database.database_model import Images, ImageType
|
||||
from src.common.logger import get_logger
|
||||
@@ -778,7 +778,7 @@ class EmojiManager:
|
||||
|
||||
decision_result = await emoji_manager_emotion_judge_llm.generate_response(
|
||||
emoji_replace_prompt,
|
||||
options=LLMGenerationOptions(temperature=0.8, max_tokens=600),
|
||||
options=LLMGenerationOptions(max_tokens=600),
|
||||
)
|
||||
decision = decision_result.response
|
||||
logger.info(f"[决策] 结果: {decision}")
|
||||
@@ -853,7 +853,6 @@ class EmojiManager:
|
||||
prompt,
|
||||
image_base64,
|
||||
"jpg",
|
||||
options=LLMImageOptions(temperature=0.5),
|
||||
)
|
||||
description = description_result.response
|
||||
else:
|
||||
@@ -865,7 +864,6 @@ class EmojiManager:
|
||||
prompt,
|
||||
image_base64,
|
||||
image_format,
|
||||
options=LLMImageOptions(temperature=0.5),
|
||||
)
|
||||
description = description_result.response
|
||||
except Exception as e:
|
||||
@@ -886,7 +884,6 @@ class EmojiManager:
|
||||
filtration_prompt,
|
||||
image_base64,
|
||||
image_format,
|
||||
options=LLMImageOptions(temperature=0.3),
|
||||
)
|
||||
llm_response = filtration_result.response
|
||||
except Exception as e:
|
||||
|
||||
@@ -9,7 +9,6 @@ import random
|
||||
from src.chat.message_receive.chat_manager import chat_manager
|
||||
from src.cli.maisaka_cli_sender import CLI_PLATFORM_NAME, render_cli_message
|
||||
from src.common.data_models.image_data_model import MaiEmoji
|
||||
from src.common.data_models.llm_service_data_models import LLMGenerationOptions
|
||||
from src.common.logger import get_logger
|
||||
from src.common.utils.utils_image import ImageUtils
|
||||
from src.services import send_service
|
||||
@@ -18,7 +17,6 @@ from .emoji_manager import (
|
||||
_normalize_emoji_tag_text,
|
||||
_serialize_emoji_for_hook,
|
||||
emoji_manager,
|
||||
emoji_manager_emotion_judge_llm,
|
||||
)
|
||||
|
||||
logger = get_logger("emoji_maisaka_tool")
|
||||
@@ -123,56 +121,6 @@ def _normalize_emotions(emoji: MaiEmoji) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
def _build_recent_context_text(context_texts: Sequence[str], max_items: int = 5) -> str:
|
||||
"""构建供情绪判断使用的最近上下文文本。"""
|
||||
|
||||
normalized_items = [str(item).strip() for item in context_texts if str(item).strip()]
|
||||
if not normalized_items:
|
||||
return ""
|
||||
return "\n".join(normalized_items[-max_items:])
|
||||
|
||||
|
||||
async def _select_emoji_with_llm(
|
||||
*,
|
||||
sampled_emojis: Sequence[MaiEmoji],
|
||||
reasoning: str,
|
||||
context_text: str,
|
||||
) -> tuple[MaiEmoji, str]:
|
||||
"""让模型在采样表情中选择更合适的情绪标签。"""
|
||||
|
||||
emotion_map: dict[str, list[MaiEmoji]] = {}
|
||||
for emoji in sampled_emojis:
|
||||
for emotion in _normalize_emotions(emoji):
|
||||
emotion_map.setdefault(emotion, []).append(emoji)
|
||||
|
||||
available_emotions = list(emotion_map.keys())
|
||||
if not available_emotions:
|
||||
return random.choice(list(sampled_emojis)), ""
|
||||
|
||||
prompt = (
|
||||
"你正在为聊天场景选择一个最合适的表情包情绪标签。\n"
|
||||
f"发送原因:{reasoning or '辅助表达当前语气和情绪'}\n"
|
||||
f"最近聊天记录:\n{context_text or '(暂无额外上下文)'}\n\n"
|
||||
"可选情绪标签如下:\n"
|
||||
f"{chr(10).join(available_emotions)}\n\n"
|
||||
"请只返回一个最匹配的情绪标签,不要解释。"
|
||||
)
|
||||
|
||||
try:
|
||||
llm_result = await emoji_manager_emotion_judge_llm.generate_response(
|
||||
prompt,
|
||||
options=LLMGenerationOptions(temperature=0.3, max_tokens=60),
|
||||
)
|
||||
chosen_emotion = (llm_result.response or "").strip().strip("\"'")
|
||||
except Exception as exc:
|
||||
logger.warning(f"使用 LLM 选择表情情绪失败,将回退为随机选择: {exc}")
|
||||
chosen_emotion = ""
|
||||
|
||||
if chosen_emotion and chosen_emotion in emotion_map:
|
||||
return random.choice(emotion_map[chosen_emotion]), chosen_emotion
|
||||
return random.choice(list(sampled_emojis)), ""
|
||||
|
||||
|
||||
async def select_emoji_for_maisaka(
|
||||
*,
|
||||
requested_emotion: str = "",
|
||||
@@ -182,6 +130,8 @@ async def select_emoji_for_maisaka(
|
||||
) -> tuple[MaiEmoji | None, str]:
|
||||
"""为 Maisaka 选择一个合适的表情。"""
|
||||
|
||||
del reasoning, context_texts
|
||||
|
||||
available_emojis = list(emoji_manager.emojis)
|
||||
if not available_emojis:
|
||||
return None, ""
|
||||
@@ -200,12 +150,7 @@ async def select_emoji_for_maisaka(
|
||||
available_emojis,
|
||||
min(max(sample_size, 1), len(available_emojis)),
|
||||
)
|
||||
context_text = _build_recent_context_text(context_texts or [])
|
||||
return await _select_emoji_with_llm(
|
||||
sampled_emojis=sampled_emojis,
|
||||
reasoning=reasoning,
|
||||
context_text=context_text,
|
||||
)
|
||||
return random.choice(sampled_emojis), ""
|
||||
|
||||
|
||||
async def send_emoji_for_maisaka(
|
||||
|
||||
@@ -24,7 +24,6 @@ async def _run_expression_selector(tool_ctx: BuiltinToolRuntimeContext, system_p
|
||||
system_prompt=system_prompt,
|
||||
request_kind="expression_selector",
|
||||
max_tokens=256,
|
||||
temperature=0.1,
|
||||
)
|
||||
return (response.content or "").strip()
|
||||
|
||||
|
||||
@@ -187,7 +187,6 @@ class MaisakaChatLoopService:
|
||||
chat_system_prompt: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
is_group_chat: Optional[bool] = None,
|
||||
temperature: float = 0.5,
|
||||
max_tokens: int = 2048,
|
||||
) -> None:
|
||||
"""初始化 Maisaka 对话循环服务。
|
||||
@@ -196,11 +195,9 @@ class MaisakaChatLoopService:
|
||||
chat_system_prompt: 可选的系统提示词。
|
||||
session_id: 当前会话 ID,用于匹配会话级额外提示。
|
||||
is_group_chat: 当前会话是否为群聊。
|
||||
temperature: 规划器温度参数。
|
||||
max_tokens: 规划器最大输出长度。
|
||||
"""
|
||||
|
||||
self._temperature = temperature
|
||||
self._max_tokens = max_tokens
|
||||
self._is_group_chat = is_group_chat
|
||||
self._session_id = session_id or ""
|
||||
@@ -546,7 +543,6 @@ class MaisakaChatLoopService:
|
||||
message_factory=message_factory,
|
||||
options=LLMGenerationOptions(
|
||||
tool_options=all_tools if all_tools else None,
|
||||
temperature=self._temperature,
|
||||
max_tokens=self._max_tokens,
|
||||
response_format=response_format,
|
||||
interrupt_flag=self._interrupt_flag,
|
||||
|
||||
@@ -138,7 +138,6 @@ class MaisakaReasoningEngine:
|
||||
request_kind="timing_gate",
|
||||
interrupt_flag=None,
|
||||
max_tokens=TIMING_GATE_MAX_TOKENS,
|
||||
temperature=0.1,
|
||||
tool_definitions=tool_definitions,
|
||||
)
|
||||
|
||||
|
||||
@@ -566,7 +566,6 @@ class MaisakaHeartFlowChatting:
|
||||
interrupt_flag: asyncio.Event | None = None,
|
||||
max_tokens: int = 512,
|
||||
response_format: RespFormat | None = None,
|
||||
temperature: float = 0.2,
|
||||
tool_definitions: Optional[Sequence[ToolDefinitionInput]] = None,
|
||||
) -> ChatResponse:
|
||||
"""运行一个复制上下文的临时子代理,并在完成后立即销毁。"""
|
||||
@@ -584,7 +583,6 @@ class MaisakaHeartFlowChatting:
|
||||
chat_system_prompt=system_prompt,
|
||||
session_id=self.session_id,
|
||||
is_group_chat=self.chat_stream.is_group_session,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
)
|
||||
sub_agent.set_interrupt_flag(interrupt_flag)
|
||||
@@ -611,7 +609,6 @@ class MaisakaHeartFlowChatting:
|
||||
request_kind="reply_effect_judge",
|
||||
extra_messages=[judge_message],
|
||||
max_tokens=900,
|
||||
temperature=0.1,
|
||||
tool_definitions=[],
|
||||
)
|
||||
return (response.content or "").strip()
|
||||
|
||||
Reference in New Issue
Block a user