Merge branch 'Mai-with-u:dev' into dev

This commit is contained in:
Dawn ARC
2026-04-21 23:20:41 +08:00
committed by GitHub
40 changed files with 56 additions and 4584 deletions

View File

@@ -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 .

View File

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

View File

@@ -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:

View File

@@ -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可以容纳巨量信息并且十分通用化十分好。

View File

@@ -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` 条段落组合成“知识上下文”给问答模型。
- 太多:增加模型负担、阅读更多文字;
- 太少:信息不够充分,一般 35 比较平衡。
> 调参建议:
> - 优先在 `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/贵价模型时建议不要太大,避免并行费用过高;
- 一般 24 就能取得较好平衡。
- `enable_ppr`
是否启用个性化 PageRankPPR图检索
- `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]` 段恢复为仓库中的默认配置;
- 重启主程序,即可回到“出厂设置”。
通过本指南中的参数调节,你可以在“检索质量”“响应速度”“系统资源占用”之间找到适合自己麦麦和机器的平衡点!

View File

@@ -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 脚本中串联上述命令。该管理脚本已支持参数化与非交互调用,适合作为二次封装的基础入口。

View File

@@ -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 知识库。***

View File

@@ -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", # 消息idstr
"time": 1627545600.0, # 时间戳float
"chat_id": "abcdef123456", # 聊天IDstr
"reply_to": None, # 回复消息idstr或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_configdict或None
"is_emoji": False, # 是否为表情bool
"is_picid": False, # 是否为图片IDbool
"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`基类的定义。

View File

@@ -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`对象包含了聊天的完整信息,包括用户信息、群信息等。

View File

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

View File

@@ -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. **性能**:频繁访问的配置建议在插件初始化时获取并缓存

View File

@@ -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"
)
```

View File

@@ -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. **场景理解**:系统能理解具体的场景描述,比简单的情感分类更准确

View File

@@ -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. **上下文感知**:生成器会考虑聊天上下文和历史消息,除非你用的是自定义提示词。

View File

@@ -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数。

View File

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

View File

@@ -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` 参数接受字符串列表,用于筛选特定用户的消息

View File

@@ -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_idMD5哈希值
#### 示例
```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是用户的唯一标识应妥善保存和使用

View File

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

View File

@@ -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. **存储选项**:可以选择是否将发送的消息存储到数据库

View File

@@ -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. **错误处理**:调用工具时请做好异常处理

View File

@@ -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`基类的定义。

View File

@@ -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` 文件只应该被用户**修改**,而不是从零创建。

View File

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

View File

@@ -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` 之类的方式来导入你需要的部分。

View File

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

View File

@@ -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`
在日志中你应该能看到插件被加载的信息。虽然插件还没有任何功能,但它已经成功运行了!
![1750326700269](image/quick-start/1750326700269.png)
### 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发送回复
```
嗨!很开心见到你!😊
```
![1750332508760](image/quick-start/1750332508760.png)
> **💡 小提示**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) 的文档。
---
🎉 恭喜你!你已经成功的创建了自己的插件了!

View File

@@ -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
View File

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

View File

@@ -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:

View File

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

View File

@@ -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:

View File

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

View File

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

View File

@@ -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,

View File

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

View File

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