diff --git a/docs/snapshots/index.md b/docs/snapshots/index.md index 5eb6371..57efc1e 100644 --- a/docs/snapshots/index.md +++ b/docs/snapshots/index.md @@ -2,6 +2,7 @@ 这里存放按 AstrBot Tag 归档的文档快照。 +- [v4.23.0](/snapshots/v4.23.0/) - [v4.12.3](/snapshots/v4.12.3/) - [v4.12.2](/snapshots/v4.12.2/) - [v4.12.1](/snapshots/v4.12.1/) diff --git a/docs/snapshots/v4.23.0/SKILL.md b/docs/snapshots/v4.23.0/SKILL.md new file mode 100644 index 0000000..a6e5862 --- /dev/null +++ b/docs/snapshots/v4.23.0/SKILL.md @@ -0,0 +1,130 @@ +--- +name: skill-astrbot-dev +description: Reference + workflow notes for AstrBot plugin development (messages, platform adapters, plugin config, agent system). +metadata: + short-description: AstrBot dev reference +--- +# skill-astrbot-dev + +This skill is the source-of-truth index for AstrBot developer docs in this repo (`docs/`). + +Goal: when this skill is selected, immediately ground on the minimum required docs + code entrypoints, +avoid duplicated reading, and always prefer code as the final authority. + +## When to use + +Use this skill when you ask for help with: + +- AstrBot plugin structure, decorators/hooks, lifecycle, schema, sessions +- Message model/event flow and message-chain conversion +- Platform adapter interface and message conversion patterns +- Agent topics (tools/providers/personas/subagents/sandbox/cron/context compression) + +## Mandatory workflow (use this every time) + +1. Start from a single entrypoint (avoid broad loading): + - Site index: `docs/index.md` + - Core concepts: `docs/design_standards/core_concepts.md` +2. Pick one topic folder and stay focused: + - Agent system: `docs/agent/` + - Plugin config: `docs/plugin_config/` + - Messages: `docs/messages/` + - Platform adapters: `docs/platform_adapters/` +3. For Agent Runner (v4.7.0+): `docs/agent/agent-runner.md` +3. If the user targets a specific AstrBot version, cross-check: + - `docs/snapshots//` +4. If docs and code disagree, treat code as truth: + - Core code lives under `astrbotcore/astrbot/core/` (read only the needed files) + +## STRONGLY ADVISED: use AstrBot SDK while writing plugins + +When writing plugin code, strongly advised to install AstrBot SDK locally and use it for API reference, +signature lookup, and IDE auto-completion. + +```powershell +python -m pip install -U astrbot +``` + +Use SDK symbols first when implementing hooks, provider/context calls, and agent runner integration. +This helps reduce guesswork and signature mismatch. + +If AstrBot source code in this repo is available, still treat repo code as higher priority than package docs. + +## Plugin project structure (strongly advised) + +A standard AstrBot plugin project should include: + +- `main.py`: entrypoint. Implement plugin startup and primary features here. +- `metadata.yaml`: plugin metadata (name, version, author, repo, description). +- `README.md`: installation, usage, feature overview, and dev links. +- `.gitignore`: ignore Python cache (`__pycache__`) and IDE config files. +- `LICENSE`: open-source license file. + +## `metadata.yaml` minimal template + +```yaml +name: astrbot_plugin_helloworld # 插件唯一识别名,最好以 astrbot_plugin_ 前缀开头 +display_name: helloworld # 展示名(v4.5.0+) +desc: AstrBot 插件示例。 # 插件简短描述 +version: v1.3.0 # 版本号:v1.1.1 或 v1.1 +author: Soulter # 作者 +repo: https://github.com/Soulter/helloworld # 插件的仓库地址 +``` + +## Code rules for plugin implementation + +- Use `async def` for handlers/hooks/tool functions. +- Keep `main.py` focused on plugin entry and orchestration; extract complex logic into submodules. +- Add type hints for public methods and hook signatures. +- Do not hardcode provider IDs or secrets; expose configurable fields in `_conf_schema.json`. +- Prefer small, testable functions over large monolithic handler bodies. +- Keep README and metadata consistent with actual plugin behavior and version. +-If you are writing AstrBot core code instead of plugins, you must submit a PR to https://github.com/AstrBotDevs/AstrBot-docs if the changes require doc updates (for instance: new hooks, new APIs, new features, platform adapter changes, and so on). If you don't see the docs repo, please remind the user to clone the docs-repo and add it to the workspace. +## Hooks: avoid missing / outdated references + +There are two different "hook" layers you must not mix up: + +- Plugin event hooks (decorators): `docs/plugin_config/hooks.md` +- Agent runner hooks (`BaseAgentRunHooks`): `docs/agent/agent-related-hooks.md` + +If you need a complete hook inventory (because context may be truncated), generate it locally: + +```powershell +python scripts/generate_hook_inventory.py +``` + +This writes to `docs/.tmp/hook_inventory/` (gitignored). Use it as a scratchpad for writing/updating docs; +do not reference `.tmp` paths as public documentation URLs. + +## High-signal code entrypoints (open only when needed) + +- Event hooks registration + signatures: `astrbotcore/astrbot/core/star/register/star_handler.py` +- Event types: `astrbotcore/astrbot/core/star/star_handler.py` +- Agent runners + hook call order: `astrbotcore/astrbot/core/agent/runners/` +- Agent hook interface: `astrbotcore/astrbot/core/agent/hooks.py` +- Main agent build (sandbox/cron/tools): `astrbotcore/astrbot/core/astr_main_agent.py` +- Skills system (AstrBot runtime skills): `astrbotcore/astrbot/core/skills/skill_manager.py` +- Subagents config loading: `astrbotcore/astrbot/core/subagent_orchestrator.py` + +## v4.5.7+ New Tool Definition Pattern + +推荐使用 dataclass 模式定义 Tool(见 `docs/design_standards/core_concepts.md` 第7节): + +```python +from pydantic.dataclasses import dataclass +from astrbot.core.agent.tool import FunctionTool + +@dataclass +class MyTool(FunctionTool): + name: str = "my_tool" + description: str = "工具描述" + parameters: dict = {...} + + async def call(self, context, **kwargs) -> str: + return "结果" +``` + +注册:`self.context.add_llm_tools(MyTool())` + +装饰器方式仍然支持,但推荐新项目使用 dataclass 模式。 + diff --git a/docs/snapshots/v4.23.0/Storage & Utils/file_storage.md b/docs/snapshots/v4.23.0/Storage & Utils/file_storage.md new file mode 100644 index 0000000..a248d0d --- /dev/null +++ b/docs/snapshots/v4.23.0/Storage & Utils/file_storage.md @@ -0,0 +1,29 @@ +--- +category: storage +--- + +# 文件存储规范 + +对于大文件、日志或插件特有的资源文件,AstrBot 建议遵循以下存储规范。 + +### 目录规范 + +所有插件特有的文件应存储在以下目录: +`data/plugin_data/{plugin_name}/` + +### 获取存储路径 + +建议在插件中使用以下方式获取路径,以确保兼容性: + +```python +from astrbot.core.utils.astrbot_path import get_astrbot_data_path + +# 获取插件专属数据目录 +plugin_data_path = get_astrbot_data_path() / "plugin_data" / self.name +plugin_data_path.mkdir(parents=True, exist_ok=True) # 确保目录存在 +``` + +### 注意事项 + +- 不要将大文件直接存储在 `docs/` 或插件根目录下。 +- 建议定期清理不再使用的临时文件。 diff --git a/docs/snapshots/v4.23.0/Storage & Utils/kv_storage.md b/docs/snapshots/v4.23.0/Storage & Utils/kv_storage.md new file mode 100644 index 0000000..62e98f3 --- /dev/null +++ b/docs/snapshots/v4.23.0/Storage & Utils/kv_storage.md @@ -0,0 +1,21 @@ +--- +category: storage +--- + +# 键值对存储 (KV Storage) + +AstrBot 为插件提供了简单易用的 KV 存储接口,适合存储配置、轻量级状态或用户数据。 + +### 核心接口 (>= v4.9.2) + +这些方法在插件类(继承自 `Star`)中可以直接调用: + +- `await self.put_kv_data(key: str, value: Any)`: 存储数据。 +- `await self.get_kv_data(key: str, default: Any = None) -> Any`: 获取数据。 +- `await self.delete_kv_data(key: str)`: 删除数据。 + +### 特点 + +- **隔离性**: 数据按插件 ID 隔离,不同插件之间的 Key 不会冲突。 +- **持久化**: 数据会自动持久化到 `data/metadata/kv_storage.db`(或相应目录)。 +- **异步**: 接口均为异步方法。 diff --git a/docs/snapshots/v4.23.0/Storage & Utils/text_to_image.md b/docs/snapshots/v4.23.0/Storage & Utils/text_to_image.md new file mode 100644 index 0000000..a3203a9 --- /dev/null +++ b/docs/snapshots/v4.23.0/Storage & Utils/text_to_image.md @@ -0,0 +1,109 @@ +# 文转图 (Text to Image) + +将文本或 HTML 模板渲染为图片。 + +## 插件方法(Star) + +### `text_to_image` + +```python +async def text_to_image(self, text: str, return_url: bool = True) -> str +``` + +- 内部调用:`html_renderer.render_t2i(...)` +- 使用当前激活模板:`t2i_active_template` +- `return_url=True` 返回可发送的 URL;`False` 返回本地文件路径 +- **网络渲染失败会自动 fallback 到本地渲染** + +```python +url = await self.text_to_image("你好,AstrBot") +yield event.image_result(url) +``` + +### `html_render` + +```python +async def html_render(self, tmpl: str, data: dict, return_url: bool = True, options: dict | None = None) -> str +``` + +- 内部调用:`html_renderer.render_custom_template(...)` +- 适合自定义 HTML + Jinja2 模板渲染 + +```python +tmpl = """ +
+

{{ title }}

+ +
+""" +url = await self.html_render(tmpl, {"title": "Todo", "items": ["吃饭", "睡觉"]}) +yield event.image_result(url) +``` + +## SDK 方法(`html_renderer`) + +```python +from astrbot.api import html_renderer +``` + +### 初始化 + +```python +await html_renderer.initialize() +``` + +### 默认文转图 + +```python +await html_renderer.render_t2i( + text: str, + use_network: bool = True, + return_url: bool = False, + template_name: str | None = None, +) +``` + +- `use_network=True` 先走网络渲染;失败时 fallback 到本地渲染 +- `return_url=False` 时返回本地路径 + +### 自定义模板渲染 + +```python +await html_renderer.render_custom_template( + tmpl_str: str, + tmpl_data: dict, + return_url: bool = False, + options: dict | None = None, +) +``` + +## 渲染选项(`html_render` / `render_custom_template`) + +`options` 透传给截图参数(Playwright 风格): + +- `timeout` +- `type`: `"jpeg" | "png"` +- `quality`(仅 jpeg) +- `omit_background`(仅 png) +- `full_page` +- `clip` +- `animations`: `"allow" | "disabled"` +- `caret`: `"hide" | "initial"` +- `scale`: `"css" | "device"` + +默认值(未传 `options` 时): + +```python +{"full_page": True, "type": "jpeg", "quality": 40} +``` + +## 模板管理方法 + +`TemplateManager` 提供模板 CRUD: + +- `list_templates()` +- `get_template(name)` +- `create_template(name, content)` +- `update_template(name, content)` +- `delete_template(name)` +- `reset_default_template()` diff --git a/docs/snapshots/v4.23.0/agent/agent-registration.md b/docs/snapshots/v4.23.0/agent/agent-registration.md new file mode 100644 index 0000000..09bda1f --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/agent-registration.md @@ -0,0 +1,58 @@ +--- +category: agent +--- + +# Agent 注册(register_agent) + +把子智能体注册成 handoff tool,供主模型在工具调用阶段转交任务。 + +## 何时使用 + +- 推荐优先:`docs/agent/subagents.md`(配置式,支持 `provider_id`、运维更稳定)。 +- 再用 `register_agent`:需要在插件代码里动态组装 Agent 或动态挂载 run hooks。 + +## API + +```python +register_agent(name: str, instruction: str, tools: list[str | FunctionTool] | None = None, run_hooks: BaseAgentRunHooks | None = None) +``` + +## 最小示例 + +```python +from astrbot.core.star.register.star_handler import register_agent + +@register_agent( + name="writer", + instruction="你是技术写作子智能体,输出精简、可执行内容。", + tools=["get_weather"], +) +async def writer_agent(event): + return None +``` + +## 给该 Agent 追加专属工具 + +```python +@writer_agent.llm_tool(name="rewrite_text") +async def rewrite_text(event, text: str): + """重写文本。 + + Args: + text(string): 原文 + """ + return f"rewrite: {text}" +``` + +## 当前执行路径(按源码) + +- `register_agent` 会创建 `Agent` + `HandoffTool`,并加入全局工具列表。 +- 运行时 handoff 走 `tool_loop_agent(...)`,核心来自 `instruction/tools/run_hooks`。 +- 当前实现中,`@register_agent` 装饰的函数体不会作为 handoff 主执行入口。 + +## tips + +- `register_agent` 属于 core 注册接口,不在 `astrbot.api.event.filter` 导出。 +- `tools=["..."]` 里的字符串工具名必须已注册;未注册会被忽略,不会自动报错。 +- 想给子智能体单独指定 `provider_id`,优先走配置式 `subagents`。 +- `@writer_agent.llm_tool(...)` 的 docstring 必须写 `Args` 类型(例如 `text(string)`),否则 schema 解析会失败。 diff --git a/docs/snapshots/v4.23.0/agent/agent-related-hooks.md b/docs/snapshots/v4.23.0/agent/agent-related-hooks.md new file mode 100644 index 0000000..6db27f7 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/agent-related-hooks.md @@ -0,0 +1,88 @@ +--- +category: agent +--- +# Agent Related Hooks + + Agent 请求/工具循环直接相关的 hooks。 + +## Plugin Hooks + +### LLM 请求阶段 + +- `@filter.on_waiting_llm_request()` +- `@filter.on_llm_request()` +- `@filter.on_llm_response()` + +```python +from astrbot.api.event import filter, AstrMessageEvent +from astrbot.api.provider import ProviderRequest, LLMResponse + +@filter.on_waiting_llm_request() +async def on_waiting(self, event: AstrMessageEvent) -> None: ... + +@filter.on_llm_request() +async def on_req(self, event: AstrMessageEvent, request: ProviderRequest) -> None: ... + +@filter.on_llm_response() +async def on_resp(self, event: AstrMessageEvent, response: LLMResponse) -> None: ... +``` + +### Tool 调用阶段 + +- `@filter.on_using_llm_tool()` +- `@filter.on_llm_tool_respond()` + +```python +from astrbot.api.event import filter, AstrMessageEvent +from astrbot.core.agent.tool import FunctionTool +from mcp.types import CallToolResult + +@filter.on_using_llm_tool() +async def on_tool_start(self, event: AstrMessageEvent, tool: FunctionTool, tool_args: dict | None) -> None: ... + +@filter.on_llm_tool_respond() +async def on_tool_end(self, event: AstrMessageEvent, tool: FunctionTool, tool_args: dict | None, tool_result: CallToolResult | None) -> None: ... +``` + +### 结果发送阶段 + +- `@filter.on_decorating_result()` +- `@filter.after_message_sent()` + +```python +from astrbot.api.event import filter, AstrMessageEvent + +@filter.on_decorating_result() +async def on_decorating(self, event: AstrMessageEvent) -> None: ... + +@filter.after_message_sent() +async def after_sent(self, event: AstrMessageEvent) -> None: ... +``` + +## Agent Runner Hooks + +用于 `context.tool_loop_agent(..., agent_hooks=...)` 的运行期扩展。 + +```python +from astrbot.core.agent.hooks import BaseAgentRunHooks +from astrbot.core.agent.run_context import ContextWrapper +from astrbot.core.agent.tool import FunctionTool +from astrbot.core.provider.entities import LLMResponse +import mcp + +class MyAgentHooks(BaseAgentRunHooks): + async def on_agent_begin(self, run_context: ContextWrapper) -> None: ... + async def on_tool_start(self, run_context: ContextWrapper, tool: FunctionTool, tool_args: dict | None) -> None: ... + async def on_tool_end(self, run_context: ContextWrapper, tool: FunctionTool, tool_args: dict | None, tool_result: mcp.types.CallToolResult | None) -> None: ... + async def on_agent_done(self, run_context: ContextWrapper, llm_response: LLMResponse) -> None: ... +``` + +## 主 Agent 默认映射关系 + +- `on_tool_start` -> `@filter.on_using_llm_tool()` +- `on_tool_end` -> `@filter.on_llm_tool_respond()` +- `on_agent_done` -> `@filter.on_llm_response()` + +## MUST + +- Hook 处理函数必须使用 `async def`。 diff --git a/docs/snapshots/v4.23.0/agent/agent-runner.md b/docs/snapshots/v4.23.0/agent/agent-runner.md new file mode 100644 index 0000000..0e39ef5 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/agent-runner.md @@ -0,0 +1,48 @@ +--- +category: agent +--- + +# Agent Runner 架构 (v4.7.0+) + +Agent Runner 是 AstrBot 中用于执行 Agent 的组件。从 v4.7.0 起,Dify、Coze、阿里云百炼应用迁移到 Agent Runner 层,解决了与 AstrBot 内置 Agent 功能的冲突。 + +## 架构理解 + +- **Chat Provider**: 负责「说话」,是单轮补全接口 +- **Agent Runner**: 负责「思考 + 做事」,是循环(感知 → 规划 → 执行 → 观察 → 再规划) + +Dify、Coze、百炼应用、DeerFlow 等平台已内置此循环,作为 Agent Runner 接入可避免与 AstrBot 内置 Agent 冲突。 + +## 支持的 Agent Runner + +| Runner | 说明 | +|--------|------| +| AstrBot 内置 | 默认,支持 MCP、知识库、网页搜索 | +| Dify | Dify Agent 应用 | +| Coze | Coze Bot | +| 阿里云百炼应用 | 百炼 Agent 应用 | +| DeerFlow | DeerFlow 智能体 | + +## 创建 Agent Runner + +WebUI → 模型提供商 → 新增提供商 → Agent 执行器 → 选择平台 → 填写配置 + +## 切换默认 Agent Runner + +WebUI → 配置 → Agent 执行方式 → 选择执行器类型 → 保存 + +## 插件侧使用 + +```python +# 获取当前会话使用的 Agent Runner +runner = self.context.get_using_agent_runner(umo=event.unified_msg_origin) + +# 或者通过 provider_id 获取 +runner = self.context.get_agent_runner_by_id(runner_id="your_runner_id") +``` + +## 注意事项 + +- Agent Runner 会调用 Chat Provider 接口 +- 切换 Agent Runner 后,部分 AstrBot 功能(MCP、知识库、网页搜索)可能不可用(取决于 Runner 实现) +- AstrBot 内置 Agent Runner 支持全部功能 diff --git a/docs/snapshots/v4.23.0/agent/context-compression.md b/docs/snapshots/v4.23.0/agent/context-compression.md new file mode 100644 index 0000000..086ff43 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/context-compression.md @@ -0,0 +1,50 @@ + +# 上下文控制与压缩 + + +## 1 `context.tool_loop_agent(...)` + +用于运行 Agent 工具循环,同时传入上下文压缩参数。 + +```python +await self.context.tool_loop_agent(event=event, chat_provider_id=prov_id, prompt="...", enforce_max_turns=20, truncate_turns=2, llm_compress_keep_recent=6) +``` + +可用压缩参数(都可选): + +- `enforce_max_turns: int`:最多保留多少轮对话(`-1` 不限制)。 +- `truncate_turns: int`:触发截断时一次丢弃多少轮。 +- `llm_compress_instruction: str | None`:LLM 压缩时的摘要指令。 +- `llm_compress_keep_recent: int`:LLM 压缩时保留最近多少条消息不摘要。 +- `llm_compress_provider: Provider | None`:用于压缩摘要的模型 provider。 +- `custom_token_counter: TokenCounter | None`:自定义 token 计数器。 +- `custom_compressor: ContextCompressor | None`:自定义压缩器。 + +## 2`context.get_provider_by_id(provider_id)` + +用于拿到压缩模型实例,再传给 `llm_compress_provider`。 + +```python +compress_prov = self.context.get_provider_by_id("openai/gpt-4o-mini") +``` + +## 3 `context.get_current_chat_provider_id(umo)` + +用于获取当前会话正在使用的对话 provider id,常用于给 `tool_loop_agent` 传 `chat_provider_id`。 + +```python +chat_provider_id = await self.context.get_current_chat_provider_id(event.unified_msg_origin) +``` + +## 4 `context.get_config(umo)` +用于读取当前会话配置,按需决定压缩参数。 +```python +cfg = self.context.get_config(event.unified_msg_origin) +``` +## 示例 +```python +umo = event.unified_msg_origin +chat_prov = await self.context.get_current_chat_provider_id(umo) +compress_prov = self.context.get_provider_by_id("your_compress_provider_id") +resp = await self.context.tool_loop_agent(event=event, chat_provider_id=chat_prov, prompt="总结最近讨论并给出下一步", enforce_max_turns=24, truncate_turns=2, llm_compress_instruction="保留任务结论、待办、关键约束", llm_compress_keep_recent=8, llm_compress_provider=compress_prov) +``` \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/agent/conversation.md b/docs/snapshots/v4.23.0/agent/conversation.md new file mode 100644 index 0000000..2da96ab --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/conversation.md @@ -0,0 +1,52 @@ +--- +category: agent +--- + +# 会话与对话分支(Conversation) + +插件侧通过 `self.context.conversation_manager` 管理会话分支;会话标识使用 `event.unified_msg_origin`(`umo`)。 + +## 插件可用入口 + +```python +conv_mgr = self.context.conversation_manager +umo = event.unified_msg_origin +``` + +## ConversationManager 可用方法 + +- `register_on_session_deleted(callback: Callable[[str], Awaitable[None]]) -> None`:注册会话删除后的级联清理回调。 +- `new_conversation(unified_msg_origin: str, platform_id: str | None = None, content: list[dict] | None = None, title: str | None = None, persona_id: str | None = None) -> str`:新建分支并切换为当前分支。 +- `switch_conversation(unified_msg_origin: str, conversation_id: str) -> None`:切换当前分支。 +- `delete_conversation(unified_msg_origin: str, conversation_id: str | None = None) -> None`:删除指定分支;不传 `conversation_id` 时删除当前分支。 +- `delete_conversations_by_user_id(unified_msg_origin: str) -> None`:删除该会话下全部分支。 +- `get_curr_conversation_id(unified_msg_origin: str) -> str | None`:读取当前分支 ID。 +- `get_conversation(unified_msg_origin: str, conversation_id: str, create_if_not_exists: bool = False) -> Conversation | None`:读取分支对象。 +- `get_conversations(unified_msg_origin: str | None = None, platform_id: str | None = None) -> list[Conversation]`:列出分支。 +- `get_filtered_conversations(page: int = 1, page_size: int = 20, platform_ids: list[str] | None = None, search_query: str = "", **kwargs) -> tuple[list[Conversation], int]`:分页 + 条件过滤。 +- `update_conversation(unified_msg_origin: str, conversation_id: str | None = None, history: list[dict] | None = None, title: str | None = None, persona_id: str | None = None, token_usage: int | None = None) -> None`:更新历史/标题/persona/token_usage。 +- `add_message_pair(cid: str, user_message: UserMessageSegment | dict, assistant_message: AssistantMessageSegment | dict) -> None`:向指定分支追加一组 user/assistant 消息。 +- `get_human_readable_context(unified_msg_origin: str, conversation_id: str, page: int = 1, page_size: int = 10) -> tuple[list[str], int]`:获取分页后的可读上下文。 + +## 最小示例 + +```python +cid = await self.context.conversation_manager.get_curr_conversation_id(event.unified_msg_origin) +``` + +```python +cid = await self.context.conversation_manager.new_conversation(event.unified_msg_origin, title="新分支") +``` + +```python +await self.context.conversation_manager.update_conversation(event.unified_msg_origin, conversation_id=cid, title="重命名", persona_id="assistant_default") +``` + +```python +contexts, total_pages = await self.context.conversation_manager.get_human_readable_context(event.unified_msg_origin, cid, page=1, page_size=10) +``` + +## MUST + +- 所有分支操作必须使用当前会话的 `umo`,不要跨会话复用 `conversation_id`。 +- 更新历史时必须传 OpenAI 风格 `list[dict]` 消息结构。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/agent/cron.md b/docs/snapshots/v4.23.0/agent/cron.md new file mode 100644 index 0000000..e55dfd4 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/cron.md @@ -0,0 +1,16 @@ + +Cron是用于通过定时任务执行逻辑或唤醒AI的功能。AI 任务触发会生成 `CronMessageEvent`识别并拦截由定时任务触发的 AI 回复 +## 插件开发 API +通过 `self.context.cron_manager` 调用: +### 1. 注册Python函数 +```python +await cron_mgr.add_basic_job(name="任务名", cron_expression="*/5 * * * *", handler=self.your_method, payload={"key": "value"}, persistent=False) +``` +### 2.注册AI唤醒 +```python +await cron_mgr.add_active_job(name="AI 任务", cron_expression="0 8 * * *", payload={"session": "UMO", "note": "指令"}, run_once=False) +``` +### 3. 维护方法 +- `delete_job(job_id: str)`: 删除 +- `list_jobs(job_type: str = None)`: 列表 +- `update_job(job_id: str, **kwargs)`: 更新 diff --git a/docs/snapshots/v4.23.0/agent/index.md b/docs/snapshots/v4.23.0/agent/index.md new file mode 100644 index 0000000..ec36bf5 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/index.md @@ -0,0 +1,49 @@ +--- +category: agent +--- + +# Agent 系统概览 + +在 AstrBot 中,“Agent”指的是:**指令/系统提示(instructions)+ 工具(tools)+ 模型提供商(providers)+ 运行时能力(上下文管理 / 子智能体 / 沙盒 / 定时任务)** 的组合。 + +本目录把原先以 “LLM” 为中心的内容重组为 “Agent” 视角:LLM/VLM/Embedding 等都被视为 Provider 能力的一部分,工具与运行时能力决定了 Agent 的上限与安全边界。 + +## 你大概率会从这里开始 + +- 需要让模型调用工具:`docs/agent/tools.md` +- 需要选模型/Embedding/STT/TTS:`docs/agent/providers.md` +- 需要控制上下文与压缩:`docs/agent/context-compression.md` +- 需要 Hook(事件钩子/Agent 钩子):`docs/agent/agent-related-hooks.md` +- 需要子智能体:`docs/agent/subagents.md` +- 需要代码方式注册子智能体:`docs/agent/agent-registration.md` +- 需要沙盒(computer use):`docs/agent/sandbox.md` +- 需要定时任务(主动能力):`docs/agent/cron.md` +- **v4.7.0+ Agent Runner 架构(Dify/Coze/DeerFlow)**:`docs/agent/agent-runner.md` + +## 最短示例:工具循环 Agent + +```python +llm_resp = await self.context.tool_loop_agent( + event=event, + chat_provider_id=prov_id, + prompt="把这段需求拆成 3 个可执行步骤,并给出每步输出。", + tools=ToolSet([MyTool()]), + max_steps=10, + tool_call_timeout=60, + system_prompt="你是一个严谨的工程助手。", +) +``` + +### 关键参数(只记这几个就够用) + +- `chat_provider_id`:对话模型 provider id(LLM/VLM 的入口通常在这里) +- `tools`:可用工具集合(`FunctionTool` / handoff tool / 运行时注入工具) +- `max_steps`:限制循环次数,避免无限工具调用 +- `tool_call_timeout`:单个工具调用超时 +- `system_prompt`:定义 Agent 角色、边界与输出格式 + +## 相关源码入口(以代码为准) + +- Agent runner(工具循环):`astrbotcore/astrbot/core/agent/runners/tool_loop_agent_runner.py` +- Agent hooks 接口:`astrbotcore/astrbot/core/agent/hooks.py` +- 主 Agent 构建(沙盒/定时工具注入/安全模式等):`astrbotcore/astrbot/core/astr_main_agent.py` diff --git a/docs/snapshots/v4.23.0/agent/offical-tool-list/tools.md b/docs/snapshots/v4.23.0/agent/offical-tool-list/tools.md new file mode 100644 index 0000000..595d9cd --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/offical-tool-list/tools.md @@ -0,0 +1,43 @@ +--- +category: agent +--- +# AstrBot 官方 Tool 列表 + + AstrBot Core 内置工具 + +## Computer Use + +- `astrbot_execute_shell`(`computer_use_runtime=sandbox|local`):执行 Shell 命令。 + - 示例参数:`{"command":"pwd","background":false}` +- `astrbot_execute_ipython`(`computer_use_runtime=sandbox`):在沙盒 IPython 执行代码。 + - 示例参数:`{"code":"print(1+1)","silent":false}` +- `astrbot_execute_python`(`computer_use_runtime=local`):在本地 Python 执行代码(仅管理员)。 + - 示例参数:`{"code":"print(1+1)","silent":false}` +- `astrbot_upload_file`(`computer_use_runtime=sandbox`):上传本地文件到沙盒。 + - 示例参数:`{"local_path":"C:/tmp/a.txt"}` +- `astrbot_download_file`(`computer_use_runtime=sandbox`):从沙盒下载文件。 + - 示例参数:`{"remote_path":"/workspace/out.txt","also_send_to_user":true}` + +## Knowledge Base + +- `astr_kb_search`(`kb_agentic_mode=true`):检索知识库内容。 + - 示例参数:`{"query":"AstrBot provider isolation"}` + +## Cron / Proactive Task + +- `create_future_task`(`add_cron_tools=true`):创建未来任务(周期或一次性)。 + - 示例参数:`{"note":"明早提醒我同步日报","cron_expression":"0 9 * * *"}` +- `delete_future_task`(`add_cron_tools=true`):删除未来任务。 + - 示例参数:`{"job_id":"cron_xxx"}` +- `list_future_tasks`(`add_cron_tools=true`):列出未来任务。 + - 示例参数:`{"job_type":"active_agent"}` + +## Proactive Message + +- `send_message_to_user`(平台支持主动消息时注入):主动向用户发送消息。 + - 示例参数:`{"messages":[{"type":"plain","text":"任务已完成"}]}` + +## Dynamic Handoff Tool + +- `transfer_to_`(`subagent_orchestrator.main_enable=true`):将任务移交给子智能体。 + - 示例参数:`{"input":"请处理这段文本并给出结构化结论"}` diff --git a/docs/snapshots/v4.23.0/agent/persona-resolution.md b/docs/snapshots/v4.23.0/agent/persona-resolution.md new file mode 100644 index 0000000..4a83a36 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/persona-resolution.md @@ -0,0 +1,53 @@ +--- +category: agent +--- + +# 人格解析与优先级(Persona Resolution) +系统按以下顺序解析 `persona_id`,命中即停止: + +1. 会话级:`session_service_config.persona_id`(`umo` 作用域) +2. 对话分支级:`conversation.persona_id` +3. 全局默认:`provider_settings.default_personality` + +## 插件可用入口 + +```python +umo = event.unified_msg_origin +conv_mgr = self.context.conversation_manager +``` + +## 1设置会话级 persona(最高优先级) + +使用 SDK `sp` 读写 `session_service_config`: + +```python +from astrbot.api import sp + +cfg = await sp.get_async(scope="umo", scope_id=umo, key="session_service_config", default={}) or {} +cfg["persona_id"] = "assistant_default" +await sp.put_async(scope="umo", scope_id=umo, key="session_service_config", value=cfg) +``` + +## 2设置对话分支级 persona + +```python +cid = await conv_mgr.get_curr_conversation_id(umo) +await conv_mgr.update_conversation(umo, conversation_id=cid, persona_id="assistant_default") +``` + +## 3 显式禁用人格注入 + +将 `persona_id` 设置为 `"[%None]"`(会话级或分支级都可): + +```python +await conv_mgr.update_conversation(umo, conversation_id=cid, persona_id="[%None]") +``` + +## 运行时行为要点 + +- 命中 persona 后会注入: + - `persona.prompt` -> `system_prompt` + - `persona._begin_dialogs_processed` -> 上下文前置消息 +- `webchat` 平台下,若未命中 persona 且 `persona_id != "[%None]"`,会追加 ChatUI 默认人格提示词。 +- 读写 `session_service_config` 时必须先读后改再写回,避免覆盖掉同键下其他字段(如 `llm_enabled` / `tts_enabled`)。 +- 会话操作必须使用当前 `umo`,不要跨会话复用 `conversation_id`。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/agent/persona-sets.md b/docs/snapshots/v4.23.0/agent/persona-sets.md new file mode 100644 index 0000000..12de20d --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/persona-sets.md @@ -0,0 +1,72 @@ +--- +category: agent +--- + +# Persona 集管理(插件可用) + +Persona 用于控制系统提示词、开场对话、可用 tools/skills。插件侧入口:`self.context.persona_manager`。 + +## 快速入口 + +```python +pm = self.context.persona_manager +umo = event.unified_msg_origin +``` + +## 核心操作(高频) + +### 读取 + +- `get_persona(persona_id: str)`:读取单个 persona。 +- `get_all_personas() -> list[Persona]`:列出全部 persona。 +- `get_default_persona_v3(umo: str | MessageSession | None = None) -> Personality`:读取默认 persona(按会话配置解析)。 + +### 新建 + +- `create_persona(persona_id, system_prompt, begin_dialogs=None, tools=None, skills=None, folder_id=None, sort_order=0) -> Persona` + +```python +await pm.create_persona( + persona_id="astrbot_plugin_writer", + system_prompt="你是一个技术写作助手。", + begin_dialogs=["你是谁?", "我是你的写作助手。"], + tools=None, + skills=None, +) +``` + +### 更新 + +- `update_persona(persona_id, system_prompt=None, begin_dialogs=None, tools=None, skills=None)` + +```python +# 只改 prompt 时,先读旧值再回填 tools/skills,避免被重置 +old = await pm.get_persona("astrbot_plugin_writer") +await pm.update_persona( + persona_id="astrbot_plugin_writer", + system_prompt="你是一个精炼的技术写作助手。", + tools=old.tools, + skills=old.skills, +) +``` + +### 删除 + +- `delete_persona(persona_id: str) -> None` + +## 文件夹管理(按需) + +- `create_folder / get_folder / get_folders / get_all_folders` +- `update_folder / delete_folder / get_folder_tree` +- `move_persona_to_folder / get_personas_by_folder / batch_update_sort_order` + + + + +## tips + +- `create_persona` 和 `update_persona` 参数不完全一致: + - `create` 有 `folder_id`、`sort_order`;`update` 没有。 +- `tools` / `skills` 语义:`None` = 全部可用,`[]` = 全部禁用 +- `begin_dialogs` 必须是偶数条 +- `_conf_schema.json`中有选择人设的配置 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/agent/providers.md b/docs/snapshots/v4.23.0/agent/providers.md new file mode 100644 index 0000000..5f2a782 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/providers.md @@ -0,0 +1,103 @@ +--- +category: agent +--- + +# Provider 选择与使用(插件可用) + +Provider 是模型能力入口(Chat/STT/TTS/Embedding) + +```python +ctx = self.context +umo = event.unified_msg_origin +``` + +## v4.5.7+ 新增方法 + +### 获取当前会话使用的 Chat Provider ID + +```python +prov_id = await ctx.get_current_chat_provider_id(umo) +``` + +- `await get_current_chat_provider_id(umo: str) -> str`: 最常用,直接返回当前会话的 chat provider ID + +### 简化 LLM 调用 + +```python +llm_resp = await ctx.llm_generate( + chat_provider_id=prov_id, + prompt="Hello!", + system_prompt="You are a helpful assistant.", +) +print(llm_resp.completion_text) +``` + +- `await llm_generate(chat_provider_id, prompt, contexts=None, system_prompt=None, tools=None) -> LLMResponse`: 简化的 LLM 调用接口 + +### 工具循环 Agent + +```python +llm_resp = await ctx.tool_loop_agent( + event=event, + chat_provider_id=prov_id, + prompt="搜索 AstrBot 相关信息", + tools=ToolSet([SearchTool()]), + max_steps=30, + tool_call_timeout=60, +) +``` + +- `await tool_loop_agent(event, chat_provider_id, prompt, tools, system_prompt=None, max_steps=30, tool_call_timeout=60) -> LLMResponse`: 自动处理工具调用循环 + +## 传统方法 + +### 当前会话正在使用的 Provider + +- `get_using_provider(umo: str | None = None) -> Provider | None`: 拿 chat provider 实例 +- `get_using_stt_provider(umo: str | None = None) -> STTProvider | None` +- `get_using_tts_provider(umo: str | None = None) -> TTSProvider | None` + +### 按 ID 读取 Provider + +- `get_provider_by_id(provider_id: str)`: 按 ID 获取 provider(可能是 chat/stt/tts/embedding/rerank) + +```python +prov = ctx.get_provider_by_id("your_provider_id") +``` + +### 列表查询(用于配置页或校验) + +- `get_all_providers() -> list[Provider]` +- `get_all_stt_providers() -> list[STTProvider]` +- `get_all_tts_providers() -> list[TTSProvider]` +- `get_all_embedding_providers() -> list[EmbeddingProvider]` + +## v4.7.0+ Agent Runner 相关 + +```python +# 获取当前会话使用的 Agent Runner +runner = ctx.get_using_agent_runner(umo=event.unified_msg_origin) + +# 或者通过 ID 获取 +runner = ctx.get_agent_runner_by_id(runner_id="your_runner_id") +``` + +## `_conf_schema.json` 集成 + +涉及 provider 选择的插件,建议在 `_conf_schema.json` 暴露配置项: + +```json +{ + "provider_id": { + "description": "选择模型提供商", + "type": "string", + "_special": "select_provider" + } +} +``` + +## Tips + +- 会话内调用必须优先传 `umo`,否则会回退到默认配置,可能拿到错误 provider +- `get_provider_by_id` 返回的不一定是 chat provider,传给 `tool_loop_agent` 前要确保是 chat provider id +- 不要把 provider id 硬编码在代码里,优先从 `_conf_schema.json` 配置读取 diff --git a/docs/snapshots/v4.23.0/agent/registe tools.md b/docs/snapshots/v4.23.0/agent/registe tools.md new file mode 100644 index 0000000..0d16f3f --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/registe tools.md @@ -0,0 +1,106 @@ +# Tools(函数调用) +Tool 是让大语言模型调用外部能力(检索、计算、执行命令、文件处理)的机制。 +## 两种定义方式 +- 类方式:继承 `FunctionTool` +- 装饰器方式:`@filter.llm_tool(...)` +## 方式一:类定义 Tool + +```python +from pydantic import Field +from pydantic.dataclasses import dataclass + +from astrbot.core.agent.run_context import ContextWrapper +from astrbot.core.agent.tool import FunctionTool, ToolExecResult +from astrbot.core.astr_agent_context import AstrAgentContext + + +@dataclass +class BilibiliTool(FunctionTool[AstrAgentContext]): + name: str = "bilibili_videos" + description: str = "A tool to fetch Bilibili videos." + parameters: dict = Field( + default_factory=lambda: { + "type": "object", + "properties": { + "keywords": { + "type": "string", + "description": "Keywords to search for Bilibili videos.", + } + }, + "required": ["keywords"], + } + ) + + async def call( + self, + context: ContextWrapper[AstrAgentContext], + **kwargs, + ) -> ToolExecResult: + return "1. 视频标题:如何使用 AstrBot\n视频链接:xxxxxx" +``` + +## 注册到 AstrBot(全局可调用) + +如果希望主对话中的模型自动感知并调用该 Tool,需要注册到全局工具池。 + +```python +class MyPlugin(Star): + def __init__(self, context: Context): + super().__init__(context) + self.context.add_llm_tools(BilibiliTool()) +``` + +兼容旧版本(不推荐新项目继续使用): + +```python +tool_mgr = self.context.provider_manager.llm_tools +tool_mgr.func_list.append(BilibiliTool()) +``` + +## 方式二:装饰器定义并注册 + +```python +from astrbot.api.event import filter, AstrMessageEvent + +@filter.llm_tool(name="get_weather") +async def get_weather(self, event: AstrMessageEvent, location: str): + """获取天气信息。 + + Args: + location(string): 地点 + """ + resp = self.get_weather_from_api(location) + yield event.plain_result("天气信息: " + resp) +``` + +`Args` 中格式必须是 `参数名(类型): 描述`。 + +支持类型: + +- `string` +- `number` +- `object` +- `boolean` +- `array` +- `array[string]`(4.5.7+) + +## 不注册也可内部使用 + +如果 Tool 只用于插件内部流程(例如你自己调用 `tool_loop_agent`),可以不注册全局,直接传 `ToolSet`。 + +```python +from astrbot.core.agent.tool import ToolSet + +llm_resp = await self.context.tool_loop_agent( + event=event, + chat_provider_id=await self.context.get_current_chat_provider_id(event.unified_msg_origin), + prompt="请调用 bilibili_videos 工具搜索 AstrBot 教程", + tools=ToolSet([BilibiliTool()]), +) +``` + +这种方式下 Tool 只在这次调用中可见,不会进入全局 `llm_tools`。 + +## tips +- `parameters` 必须是合法 JSON Schema。 +- 装饰器方式必须写规范 docstring(尤其 `Args`),否则 schema 解析失败。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/agent/sandbox.md b/docs/snapshots/v4.23.0/agent/sandbox.md new file mode 100644 index 0000000..ba1e833 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/sandbox.md @@ -0,0 +1,48 @@ +--- +category: agent +--- + +# Sandbox(插件可用) + +Sandbox 是 Agent 的计算机使用运行时(shell/python/文件上传下载)。 + +## 快速入口 + +```python +ctx = self.context +umo = event.unified_msg_origin +``` +## 底层方法(给 booter/工具实现使用) + +- `get_booter(context, session_id)` +- `get_local_booter()` +- `booter.shell.exec(command, cwd=None, env=None, timeout=30, shell=True, background=False)` +- `booter.python.exec(code, kernel_id=None, timeout=30, silent=False)` +- `booter.upload_file(path, file_name)` +- `booter.download_file(remote_path, local_path)` +- `booter.available()` + +## UMO 与当前 Sandbox 的绑定规则 + +- 当前会话标识使用 `event.unified_msg_origin`。 +- 工具执行时用 `event.unified_msg_origin` 调用 `get_booter(...)` 获取当前会话 booter。 +- `get_booter` 内部按 `session_id` 缓存:`session_booter[session_id]`。 +- 若缓存实例 `available()` 为 false,会先移除再重建。 +- `get_booter` 会读取 `context.get_config(umo=session_id)`,因此会话级配置可生效。 + +## 配置键(常用) + +- `provider_settings.computer_use_runtime`: `none | local | sandbox` +- `provider_settings.sandbox.booter`: `shipyard | boxlite` +- `provider_settings.sandbox.shipyard_endpoint` +- `provider_settings.sandbox.shipyard_access_token` +- `provider_settings.sandbox.shipyard_ttl` +- `provider_settings.sandbox.shipyard_max_sessions` + +## 易翻车点 + +- `shipyard_endpoint` 或 `shipyard_access_token` 缺失时,sandbox 工具不会注入。 +- `astrbot_execute_shell` 要求 `admin` 角色,否则返回 permission denied。 +- `astrbot_download_file(..., also_send_to_user=True)` 会发送后删除本地临时文件。 +- `local` 与 `sandbox` 是两套运行时:`local` 走 `get_local_booter()`,`sandbox` 走 `get_booter(..., umo)`。 + diff --git a/docs/snapshots/v4.23.0/agent/skills.md b/docs/snapshots/v4.23.0/agent/skills.md new file mode 100644 index 0000000..ae4e15a --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/skills.md @@ -0,0 +1,61 @@ +# Skills(能力包) +## 简介 + +Skill 是给 Agent 用的本地能力包:一个目录 + `SKILL.md`(可带 `scripts/`、`assets/`、参考文档)。 + +主 Agent 会把已启用 Skill 的名称、描述、入口文件路径注入系统提示词,让模型知道“有哪些可调用的本地能力”。 + +## 插件侧入口 + +当前没有 `self.context.skill_manager` 这类快捷入口,按需直接使用 `SkillManager`: + +```python +from astrbot.core.skills.skill_manager import SkillManager +``` + +## SkillManager 方法 + +### 查询 + +- `list_skills(active_only: bool = False, runtime: str = "local", show_sandbox_path: bool = True) -> list[SkillInfo]` + +```python +skills = SkillManager().list_skills(active_only=True, runtime="sandbox") +``` + +### 启停 + +- `set_skill_active(name: str, active: bool) -> None` + +```python +SkillManager().set_skill_active("docs4agent", True) +``` + +### 删除 + +- `delete_skill(name: str) -> None` + +```python +SkillManager().delete_skill("legacy_skill") +``` + +### 安装(zip) + +- `install_skill_from_zip(zip_path: str, overwrite: bool = True) -> str` + +```python +skill_name = SkillManager().install_skill_from_zip("D:/tmp/my_skill.zip", overwrite=True) +``` + +## Agent 注入链路 + +- 主 Agent 根据 `provider_settings.computer_use_runtime` 选择 runtime。 +- 调用 `SkillManager().list_skills(active_only=True, runtime=runtime)` 获取技能列表。 +- 调用 `build_skills_prompt(skills)` 把技能信息注入 system prompt。 +- 若当前 persona 配置了 `skills` 白名单,则会再做一次过滤。 + +## MUST + +- Skill 目录必须包含 `SKILL.md`。 +- zip 安装包必须是“单一顶层目录”,且禁止绝对路径与 `..` 路径。 +- skill 目录名必须匹配 `^[A-Za-z0-9._-]+$`。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/agent/subagents.md b/docs/snapshots/v4.23.0/agent/subagents.md new file mode 100644 index 0000000..2ad5ee3 --- /dev/null +++ b/docs/snapshots/v4.23.0/agent/subagents.md @@ -0,0 +1,70 @@ +--- +category: agent +--- + +# Subagents(子智能体 / Handoff) + +Subagent 是给主 Agent 使用的 handoff 工具。主模型通过 `transfer_to_` 把任务转交给子智能体执行。 +`from astrbot.api import agent`(详见 `docs/agent/agent-registration.md`) + +## 配置式(推荐) + +### 最小配置 + +```json +{ + "subagent_orchestrator": { + "main_enable": true, + "remove_main_duplicate_tools": false, + "router_system_prompt": "You are a task router...", + "agents": [ + { + "enabled": true, + "name": "writer", + "public_description": "负责技术文档整理与重写", + "persona_id": null, + "system_prompt": "你是文档子智能体,输出精简且结构化。", + "provider_id": "openai_gpt4o_mini", + "tools": ["search_docs", "rewrite_text"] + } + ] + } +} +``` + +### `agents[]` 字段(源码对齐) + +- `enabled`: 是否启用 +- `name`: 子智能体名;工具名会生成为 `transfer_to_` +- `public_description`: 暴露给主模型的工具描述(决定主模型是否愿意调用) +- `persona_id`: 可选;存在时优先使用 persona 的 `system_prompt/begin_dialogs/tools` +- `system_prompt`: 未命中 persona 时使用 +- `provider_id`: 可选;子智能体专用 chat provider 覆盖 +- `tools`: 子智能体可用工具名列表(字符串) + +## 运行规则 + +- `main_enable=true` 时,主 Agent 会把所有 handoff 工具加入工具集。 +- `remove_main_duplicate_tools=true` 时,会把“已分配给子智能体”的同名工具从主 Agent 工具集移除。 +- `router_system_prompt` 会拼接到主 Agent 的 `system_prompt`。 +- `provider_id` 不为空时,handoff 执行优先用该 provider;否则回退当前会话 provider。 + +## SDK/代码式(高级) + +```python +from astrbot.api import agent + +@agent(name="writer", instruction="你是写作子智能体。") +async def writer_agent(event): + return None +``` + +> 代码式注册、`run_hooks`、专属工具挂载见:`docs/agent/agent-registration.md` + +## MUST + +- `name` 必须非空,且在同一实例中保持唯一。 +- `public_description` 必须写“适用任务”,不要写空泛人设。 +- `tools` 必须显式写成字符串列表(不要依赖隐式行为)。 + + diff --git a/docs/snapshots/v4.23.0/design_standards/architecture_overview.md b/docs/snapshots/v4.23.0/design_standards/architecture_overview.md new file mode 100644 index 0000000..4c6eec1 --- /dev/null +++ b/docs/snapshots/v4.23.0/design_standards/architecture_overview.md @@ -0,0 +1,21 @@ +--- +category: design_standards +--- + +# 核心架构综述 + +AstrBot 采用基于**插件化 (Plugin-based)** 和 **事件驱动 (Event-driven)** 的架构。其核心(Core)负责协调各个管理器(Manager),并通过 `Context` 对象向插件(Star)暴露能力。 + +### 核心管理器分工 + +- **`PluginManager`**: 负责插件的加载、卸载、重载以及元数据管理。 +- **`PlatformManager`**: 管理所有已接入的消息平台适配器,负责分发事件。 +- **`ProviderManager`**: 管理大语言模型(LLM)、语音识别(STT)、语音合成(TTS)等服务提供商。 +- **`ConversationManager`**: 管理用户会话历史、上下文存储及切换。 +- **`PersonaManager`**: 管理人格设定(Persona),包括系统提示词(System Prompt)和工具配置。 + +### 核心设计原则 + +1. **解耦**: 核心系统与平台适配器、AI 提供商、插件之间高度解耦。 +2. **统一模型**: 所有的平台消息都被转化为统一的 `AstrBotMessage` 模型。 +3. **插件化**: 功能尽可能通过插件实现,核心仅提供基础调度能力。 diff --git a/docs/snapshots/v4.23.0/design_standards/best_practices.md b/docs/snapshots/v4.23.0/design_standards/best_practices.md new file mode 100644 index 0000000..845cc5e --- /dev/null +++ b/docs/snapshots/v4.23.0/design_standards/best_practices.md @@ -0,0 +1,43 @@ +--- +category: design_standards +--- + +# AI 插件开发最佳实践 + +为了确保插件的稳定性、安全性和易用性,建议遵循以下实践方案。 + +### 1. 异常处理 + +务必捕获可能的异常,并给用户明确的反馈。 + +```python +try: + # 逻辑代码 +except TimeoutError: + yield event.plain_result("⌛ 会话已超时,请重新开始。") +except Exception as e: + logger.error(f"插件执行出错: {e}") + yield event.plain_result(f"❌ 发生错误: {e}") +finally: + event.stop_event() # 已经处理过错误,通常建议停止事件继续传播 +``` + +### 2. 平台差异化 + +虽然 AstrBot 提供了统一模型,但在调用底层 SDK 功能(如 `call_action`)时,需进行环境检查: + +```python +if event.get_platform_name() == "aiocqhttp": + # 调用 OneBot 特有 API + pass +``` + +### 3. 工具 (Tools) 开发 + +- 推荐使用 `agent-as-tool` 模式。 +- 完善 Docstring,这直接决定了大模型对工具的理解能力。 +- 尽量保持工具功能的单一性。 + +### 4. 资源清理 + +在插件卸载时,应在 `terminate()` 方法中清理定时器、数据库连接或文件句柄。 diff --git a/docs/snapshots/v4.23.0/design_standards/context_usage.md b/docs/snapshots/v4.23.0/design_standards/context_usage.md new file mode 100644 index 0000000..22dc222 --- /dev/null +++ b/docs/snapshots/v4.23.0/design_standards/context_usage.md @@ -0,0 +1,30 @@ +--- +category: design_standards +--- + +# Context 对象使用规范 + +`Context` 对象是 AstrBot 的能力中枢,在插件初始化时通过 `__init__` 注入。它是插件与系统核心交互的唯一桥梁。 + +### 重要属性 + +通过 `self.context` 可以访问各个管理器: + +- `self.context.conversation_manager`: 会话管理器。 +- `self.context.persona_manager`: 人格管理器。 +- `self.context.platform_manager`: 平台管理器。 +- `self.context.provider_manager`: 提供商管理器。 + +### 核心方法 + +#### 消息与平台相关 +- `send_message(umo: str, message_chain: MessageChain)`: 向指定源主动发送消息。 +- `get_platform(platform_type: PlatformAdapterType)`: 获取指定类型的平台实例。 + +#### AI 与工具相关 +- `add_llm_tools(*tools)`: 动态注册函数工具。 +- `get_using_provider(umo)`: 获取当前使用的 LLM 提供商。 + +#### 配置与插件 +- `get_config(umo=None)`: 获取当前配置。 +- `get_all_stars()`: 获取所有已加载插件的元数据。 diff --git a/docs/snapshots/v4.23.0/design_standards/core_concepts.md b/docs/snapshots/v4.23.0/design_standards/core_concepts.md new file mode 100644 index 0000000..58549b4 --- /dev/null +++ b/docs/snapshots/v4.23.0/design_standards/core_concepts.md @@ -0,0 +1,128 @@ +# AstrBot 核心概念 API 清单 + +本文档仅列出 AstrBot 插件开发的核心功能 API。 + +### 1. 装饰器 (Decorators) + +- `@register(id, author, description, version, repo_url)`: 注册插件。 +- `@filter.command(name, alias, priority)`: 注册指令。支持带参函数。 +- `@filter.command_group(name)`: 注册指令组。 +- `@filter.event_message_type(type)`: 过滤消息类型 (`ALL`, `PRIVATE_MESSAGE`, `GROUP_MESSAGE`)。 +- `@filter.platform_adapter_type(type)`: 过滤平台类型 (如 `AIOCQHTTP`, `TELEGRAM`)。 +- `@filter.permission_type(type)`: 校验权限 (如 `ADMIN`)。 +- `@filter.regex(pattern)`: 正则匹配。 +- `@filter.llm_tool(name)`: 注册为 AI 可调用的工具。 +- `@session_waiter(timeout, record_history_chains)`: 等待下一条用户消息。 + +### 2. 消息组件 (Message Components) + +- `Plain(text)`: 纯文本。 +- `At(user_id)`: 提及用户。 +- `Image.fromFileSystem(path)` / `Image.fromURL(url)`: 图片。 +- `Record.fromFileSystem(path)`: 语音。 +- `Video.fromFileSystem(path)` / `Video.fromURL(url)`: 视频。 +- `File.fromFileSystem(path, name)`: 文件。 +- `Face(id)`: 系统表情。 +- `Reply(message_id)`: 回复特定消息。 +- `Node(uin, name, content)` / `Nodes(nodes)`: 合并转发节点 (部分平台支持)。 + +### 3. 核心对象与方法 + +**AstrMessageEvent (事件对象)** + +- 消息事件对象,包含消息内容、发送者信息、群组信息等。 +- 提供消息发送和结果构建方法。 + +**Context (核心枢纽)** + +- `context.send_message(umo, chain)`: 向指定源主动发送消息。 +- `context.get_platform(type)`: 获取指定类型的平台实例。 +- `context.get_using_provider(umo)`: 获取当前 LLM 提供商。 +- `context.add_llm_tools(*tools)`: 动态注册 AI 工具。 + +**v4.5.7+ 新增 LLM API** + +```python +umo = event.unified_msg_origin +prov_id = await self.context.get_current_chat_provider_id(umo) +``` + +- `await context.get_current_chat_provider_id(umo) -> str`: 获取当前会话使用的 chat provider ID。 +- `await context.llm_generate(chat_provider_id, prompt, contexts=None, system_prompt=None, tools=None) -> LLMResponse`: 简化的 LLM 调用。 +- `await context.tool_loop_agent(event, chat_provider_id, prompt, tools, system_prompt=None, max_steps=30, tool_call_timeout=60) -> LLMResponse`: 工具循环 Agent。 + +**MessageChain (消息链构建器)** + +- `MessageChain().message(text)`: 添加文本。 +- `MessageChain().file_image(path)`: 添加图片文件。 +- `MessageChain().at(user_id)`: 添加 At。 + +### 4. 存储与工具 (Storage & Utils) + +- `await self.get_kv_data(key, default)`: 获取插件隔离的 KV 数据。 +- `await self.put_kv_data(key, value)`: 存储插件隔离的 KV 数据。 +- `await self.delete_kv_data(key)`: 删除 KV 数据。 +- `await self.html_render(html_text=None, url=None, data=None, options=None)`: 将 HTML 字符串或网页渲染为图片。基于 Playwright。 +- `text_to_image(text)`: 将文字转为图片。 + +### 5. 系统钩子 (Hooks) + +Hooks 分为两层,不建议在"概念清单"里重复列举具体 hook 名单(容易过时): + +- 插件事件钩子(`@filter.on_*`):见 `docs/plugin_config/hooks.md` +- Agent 运行钩子(`BaseAgentRunHooks`):见 `docs/agent/agent-related-hooks.md` + +### 6. Agent 智能体 + +- Agent 相关能力(tools / providers / persona / sandbox / cron / subagents):见 `docs/agent/` +- `context.tool_loop_agent(...)`: 调用工具循环 Agent(可结合子智能体 handoff) +- v4.7.0+ Agent Runner 架构:见 `docs/agent/agent-runner.md` + +### 7. Tool 定义 (v4.5.7+ 推荐) + +推荐使用 dataclass 模式定义 Tool: + +```python +from pydantic import Field +from pydantic.dataclasses import dataclass +from astrbot.core.agent.tool import FunctionTool +from astrbot.core.agent.run_context import ContextWrapper +from astrbot.core.astr_agent_context import AstrAgentContext + +@dataclass +class MyTool(FunctionTool[AstrAgentContext]): + name: str = "my_tool" + description: str = "工具描述" + parameters: dict = Field(default_factory=lambda: { + "type": "object", + "properties": {"query": {"type": "string", "description": "参数描述"}}, + "required": ["query"], + }) + + async def call(self, context: ContextWrapper[AstrAgentContext], **kwargs) -> str: + return "结果" +``` + +### 8. 多智能体 (Multi-Agent) v4.5.7+ + +使用 agent-as-tool 模式实现多智能体: + +```python +@dataclass +class SubAgentTool(FunctionTool[AstrAgentContext]): + name: str = "sub_agent" + description: str = "子智能体描述" + parameters: dict = Field(default_factory=lambda: {...}) + + async def call(self, context: ContextWrapper[AstrAgentContext], **kwargs) -> str: + ctx = context.context.context + event = context.context.event + llm_resp = await ctx.tool_loop_agent( + event=event, + chat_provider_id=await ctx.get_current_chat_provider_id(event.unified_msg_origin), + prompt=kwargs["query"], + tools=ToolSet([SomeTool()]), + max_steps=30, + ) + return llm_resp.completion_text +``` diff --git a/docs/snapshots/v4.23.0/design_standards/event_flow.md b/docs/snapshots/v4.23.0/design_standards/event_flow.md new file mode 100644 index 0000000..16389ba --- /dev/null +++ b/docs/snapshots/v4.23.0/design_standards/event_flow.md @@ -0,0 +1,20 @@ +--- +category: design_standards +--- + +# 消息流转模型 + +AstrBot 的消息处理遵循一个清晰的流转过程。 + +### 核心流程图 + +1. **接收**: 平台适配器(Platform)接收原始消息。 +2. **转换**: 调用 `convert_message` 将其封装为 `AstrBotMessage`。 +3. **提交**: 封装为 `AstrMessageEvent` 后通过 `self.commit_event(event)` 提交到事件队列。 +4. **分发**: `PlatformManager` 按优先级将事件分发给所有插件的 Handler。 +5. **处理**: 插件执行业务逻辑。 + - 若调用 `event.stop_event()`,流程在此终止。 +6. **LLM 交互**: 若消息未被拦截,且符合 AI 触发条件,调用配置的 LLM。 +7. **结果装饰**: 发送前调用 `on_decorating_result` 钩子。 +8. **回复**: 调用 `event.send()` 或 `yield`,触发适配器的 `send` 方法。 +9. **发送**: 适配器调用平台 SDK 发送消息。 diff --git a/docs/snapshots/v4.23.0/design_standards/sandbox.md b/docs/snapshots/v4.23.0/design_standards/sandbox.md new file mode 100644 index 0000000..faf01d3 --- /dev/null +++ b/docs/snapshots/v4.23.0/design_standards/sandbox.md @@ -0,0 +1,20 @@ +--- +title: Sandbox 存储挂载与文件共享 (Sandbox Storage Mounting & File Sharing) +type: improvement +status: stable +last_updated: 2025-02-10 +related_base: agent/sandbox.md +--- + +## 概述 +在基于 Shipyard 的 Sandbox 运行时中,系统通过 Docker Volume 建立了宿主机与沙盒环境之间的共享临时目录。这一变更明确了文件在宿主机与沙盒之间流转的物理路径契约,是 `astrbot_upload_file` 等文件操作工具正常运行的基础设施保障。 + +## 存储映射契约 +为了实现宿主机与沙盒环境的高效文件交换,系统建立了以下挂载关系: +- **宿主机源路径**: `${PWD}/data/temp` (即 AstrBot 运行根目录下的临时文件夹) +- **沙盒目标路径**: `/AstrBot/data/temp` (沙盒环境内的绝对路径) + +## 变更影响分析 +1. **文件访问一致性**:AI 开发者在编写涉及沙盒文件操作的工具(Tools)时,应知晓 `/AstrBot/data/temp` 是预设的共享交换区。上传到宿主机临时目录的文件将直接映射至此路径,无需通过网络流重复传输。 +2. **底层实现透明化**:此变更解释了 `ShipyardBooter` 如何在物理层面处理文件可见性。如果开发者在非标准 Docker 环境下部署,需手动配置类似的卷挂载以维持 `astrbot_upload_file` 和 `astrbot_download_file` 的功能兼容性。 +3. **边界情况**:宿主机对 `data/temp` 的清理操作会同步反映在沙盒内。在执行长时间运行的 Agent 任务时,需注意临时文件的生命周期管理,避免因宿主机清理导致沙盒内路径失效。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/design_standards/visual_utils.md b/docs/snapshots/v4.23.0/design_standards/visual_utils.md new file mode 100644 index 0000000..1a18162 --- /dev/null +++ b/docs/snapshots/v4.23.0/design_standards/visual_utils.md @@ -0,0 +1,89 @@ +--- +category: design_standards +--- + +# 视觉与渲染工具 (Visual Utils) + +AstrBot 提供了一些工具函数,帮助插件实现更丰富的视觉表现,如 HTML 渲染图片。 + +### 1. HTML 渲染 (html_render) + +AstrBot 内置了基于 **Playwright** 的 HTML 渲染引擎,支持将 HTML 字符串(支持 Jinja2 模板)或远程网页渲染为图片。 + +#### `await self.html_render(html_text: str = None, url: str = None, data: dict = None, options: dict = None) -> str` + +- **参数**: + - `html_text`: Jinja2 格式的 HTML 模板字符串。 + - `url`: 目标网页的 URL。如果提供了 `url`,将优先使用 `url` 而忽略 `html_text`。 + - `data`: 传入模板的变量字典(仅在提供 `html_text` 时有效)。 + - `options`: 渲染选项(映射自 Playwright API)。 + - `viewport`: 视口大小,例如 `{"width": 800, "height": 600}`。 + - `selector`: 等待并截图指定的 CSS 选择器对应的元素。 + - `wait_until`: 等待页面加载的状态。可选值:`"commit"`, `"domcontentloaded"`, `"load"`, `"networkidle"` (默认)。 + - `timeout`: 截图超时时间(毫秒)。 + - `type`: 图片格式,`"jpeg"` 或 `"png"`。 + - `quality`: 仅 JPEG 有效 (0-100)。 + - `omit_background`: 是否透明背景 (仅 PNG)。 + - `full_page`: 是否截取整页 (默认为 True,如果指定了 `selector` 则失效)。 + - `clip`: 裁切区域 `{"x": 0, "y": 0, "width": 100, "height": 100}`。 + - `animations`: `"allow"` 或 `"disabled"`。 + - `scale`: `"css"` 或 `"device"`。 +- **返回值**: 渲染后的图片本地路径。 + +#### 使用示例 + +##### 渲染 HTML 模板 + +```python +TMPL = """ +
+

Hello {{ name }}!

+

This is rendered via AstrBot HTML Render.

+
+""" + +@filter.command("hello_render") +async def hello_render(self, event: AstrMessageEvent): + # 渲染 HTML 字符串并传入数据 + image_path = await self.html_render(html_text=TMPL, data={"name": event.get_sender_id()}) + + # 将结果作为图片发送 + yield event.image_result(image_path) +``` + +##### 渲染远程网页 + +```python +@filter.command("screenshot") +async def screenshot(self, event: AstrMessageEvent, site_url: str): + # 渲染指定 URL,并设置视口大小 + image_path = await self.html_render( + url=site_url, + options={"viewport": {"width": 1280, "height": 720}, "wait_until": "networkidle"} + ) + yield event.image_result(image_path) +``` + +#### 转换为 Image 组件 + +`html_render` 返回的是图片的本地路径。你可以使用 `event.image_result(path)` 快速发送,也可以手动构建 `Image` 组件: + +```python +from astrbot.api.message_components import Image + +image_path = await self.html_render(url="...") +image_comp = Image.fromFileSystem(image_path) + +# 放入消息链发送 +# yield event.chain_result([Plain("这是截图:"), image_comp]) +``` + +### 2. 文字转图片 (text_to_image) + +#### `text_to_image(text: str, return_url: bool = True) -> str` + +- **说明**: 简单的文字转图片工具。 +- **参数**: + - `text`: 要转换的文字内容。 + - `return_url`: 是否返回 URL 格式。 +- **返回值**: 图片的路径或 URL。 diff --git a/docs/snapshots/v4.23.0/index.md b/docs/snapshots/v4.23.0/index.md new file mode 100644 index 0000000..b42e256 --- /dev/null +++ b/docs/snapshots/v4.23.0/index.md @@ -0,0 +1,21 @@ +# AstrBot 开发文档 + +本仓库的文档用于给 **AI 开发者 / RAG** 提供结构化的上下文。 + +## 快速入口 + +- [核心概念](/design_standards/core_concepts) +- [架构总览](/design_standards/architecture_overview) +- [Agent(工具 / 子智能体 / 沙盒 / 定时任务)](/agent/) +- [v4.7.0+ Agent Runner 架构](/agent/agent-runner) +- [消息模型](/messages/model) +- [插件配置 Schema](/plugin_config/schema) +- [事件钩子(Hooks)](/plugin_config/hooks) +- [平台适配器接口](/platform_adapters/adapter_interface) + +## 快照 + +版本快照位于 `docs/snapshots/`,例如: + +- [快照索引](/snapshots/) +- [v4.11.2](/snapshots/v4.11.2/) diff --git a/docs/snapshots/v4.23.0/messages/components.md b/docs/snapshots/v4.23.0/messages/components.md new file mode 100644 index 0000000..ce7f2dd --- /dev/null +++ b/docs/snapshots/v4.23.0/messages/components.md @@ -0,0 +1,41 @@ +--- +category: messages +--- + +# 消息链组件 (Message Components) + +AstrBot 使用消息链(MessageChain)来描述消息结构,它是一个由多个消息段(MessagePart/Component)组成的有序列表。 + +### 核心组件及其兼容性 + +| 组件类型 | 描述 | 参数示例 | 平台兼容性建议 | +| :--- | :--- | :--- | :--- | +| `Plain` | 纯文本 | `text="Hello"` | 所有平台支持。 | +| `At` | 提及/艾特 | `user_id="xxx"` | 大多数平台支持。 | +| `Image` | 图片 | `fromFileSystem(path)`, `fromURL(url)` | 所有平台支持。URL 必须以 `http` 或 `https` 开头。 | +| `Record` | 语音 | `file="path/to/wav"` | 广泛支持。目前主要支持 `wav` 格式。 | +| `Video` | 视频 | `fromFileSystem(path)`, `fromURL(url)` | 广泛支持。常用格式为 `mp4` | +| `File` | 文件 | `file="path"`, `name="a.txt"` | 部分平台不支持。 | +| `Face` | 表情 | `id="123"` | 主要在 OneBot v11 (QQ) 平台支持。 | +| `Node/Nodes` | 合并转发节点 | `uin`, `name`, `content` | 仅 OneBot v11 支持。 | +| `Poke` | 戳一戳 | - | 主要在 OneBot v11 支持。 | +| `Reply` | 回复特定消息 | `message_id="xxx"` | 广泛支持。 | + +### 消息构建示例 + +```python +import astrbot.api.message_components as Comp + +# 方式 1:手动构建列表 +chain = [ + Comp.At(user_id=event.get_sender_id()), + Comp.Plain(" 来看这张图:"), + Comp.Image.fromURL("https://example.com/image.jpg") +] +yield event.chain_result(chain) + +# 方式 2:使用 MessageChain 流式构建 +from astrbot.api.event import MessageChain +message_chain = MessageChain().message("Hello!").file_image("path/to/image.jpg") +await self.context.send_message(event.unified_msg_origin, message_chain) +``` diff --git a/docs/snapshots/v4.23.0/messages/events.md b/docs/snapshots/v4.23.0/messages/events.md new file mode 100644 index 0000000..c8681f9 --- /dev/null +++ b/docs/snapshots/v4.23.0/messages/events.md @@ -0,0 +1,33 @@ +--- +title: 消息事件 (AstrMessageEvent) +type: improvement +status: stable +last_updated: 2024-05-22 +related_base: messages/events.md +--- + +## 概述 +`AstrMessageEvent` 是插件处理逻辑的核心上下文对象。在最新版本中,该对象的会话标识属性(`session_id` 与 `unified_msg_origin`)已重构为基于 `MessageSession` 对象的动态属性(Property),增强了会话管理的一致性。 + +## 核心属性与 Setter 契约 + +这些属性现在不仅支持读取,还支持通过 Setter 进行动态修改,且修改会自动同步到底层的 `MessageSession` 状态: + +- **`event.unified_msg_origin` (UMO)**: + - **Getter**: 返回格式为 `platform_name:message_type:session_id` 的统一标识符。 + - **Setter**: 允许通过赋值 UMO 字符串来重置事件的会话上下文。内部通过 `MessageSession.from_str(value)` 重新解析并覆盖当前 session 对象。 +- **`event.session_id`**: + - **Getter**: 获取当前会话的唯一 ID。 + - **Setter**: 直接修改当前会话 ID,此变更会立即反映在 `unified_msg_origin` 的输出中。 + +## 内部实现逻辑 + +`AstrMessageEvent` 不再在 `__init__` 中静态存储 `session_id` 和 `unified_msg_origin` 字符串,而是统一维护一个 `self.session` (`MessageSession` 类实例)。 +- **初始化**: 修正了 `MessageSession` 的拼写错误并确保其在事件创建时被正确初始化。 +- **响应式更新**: 通过 Python `@property` 装饰器,确保了 UMO 和 Session ID 始终指向同一个数据源,消除了状态不一致的风险。 + +## 变更影响分析 + +1. **动态会话切换**: 插件开发者现在可以在事件处理过程中,通过修改 `event.unified_msg_origin` 动态地将事件“重定向”到另一个会话上下文。这对于实现跨群指令触发或会话劫持逻辑至关重要。 +2. **副作用警示**: 修改 `unified_msg_origin` 会导致底层的 `platform_name` 和 `message_type` 同时发生变化。如果仅需修改用户 ID,应优先使用 `event.session_id` 的 setter。 +3. **最佳实践**: 在编写需要持久化或比对会话的逻辑时,应始终依赖 `event.unified_msg_origin` 属性,因为它现在是经过 `MessageSession` 校验的权威来源。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/messages/model.md b/docs/snapshots/v4.23.0/messages/model.md new file mode 100644 index 0000000..4394c9d --- /dev/null +++ b/docs/snapshots/v4.23.0/messages/model.md @@ -0,0 +1,31 @@ +--- +category: messages +--- + +# 消息模型 (AstrBotMessage) + +`AstrBotMessage` 是适配器层生成的标准化消息对象,它屏蔽了不同平台(QQ、飞书等)的差异,使插件可以“一次编写,到处运行”。 + +### AstrBotMessage 结构 + +在适配器中,必须填充 `AstrBotMessage` 的以下字段: + +```python +class AstrBotMessage: + type: MessageType # 消息类型(GROUP_MESSAGE 或 FRIEND_MESSAGE) + self_id: str # 机器人 ID + session_id: str # 会话 ID,决定了上下文隔离 + message_id: str # 消息 ID + group_id: str # 群组 ID(如果是私聊则为空) + sender: MessageMember # 发送者信息(含 user_id 和 nickname) + message: List[BaseMessageComponent] # 消息链(组件列表) + message_str: str # 纯文本汇总内容 + raw_message: object # 原始平台消息对象(用于 Debug 或特殊处理) + timestamp: int # 时间戳 +``` + +### 属性详解 + +- **`session_id`**: 核心字段,用于决定 LLM 对话的上下文隔离。 +- **`message_str`**: 插件处理逻辑中常用的纯文本内容。 +- **`message`**: 结构化消息内容,由各种消息组件组成。 diff --git a/docs/snapshots/v4.23.0/messages/umo.md b/docs/snapshots/v4.23.0/messages/umo.md new file mode 100644 index 0000000..3c432a9 --- /dev/null +++ b/docs/snapshots/v4.23.0/messages/umo.md @@ -0,0 +1,22 @@ +--- +category: messages +--- + +# 统一消息源 (Unified Message Origin) + +统一消息源(Unified Message Origin,简称 **UMO**)是 AstrBot 识别跨平台会话的核心标识。 + +### 格式 + +UMO 是一个格式如下的字符串: +`platform_id:message_type:session_id` + +- **`platform_id`**: 平台 ID(如 `aiocqhttp`, `qqofficial`)。 +- **`message_type`**: 消息类型(`group` 或 `private`)。 +- **`session_id`**: 会话 ID(群号或用户 ID)。 + +### 用途 + +1. **会话识别**: 唯一标识一个消息来源。 +2. **主动发送**: 通过 `context.send_message(umo, message_chain)` 向指定的源发送消息。 +3. **获取方式**: 在插件中通过 `event.unified_msg_origin` 获取。 diff --git a/docs/snapshots/v4.23.0/platform_adapters/adapter_interface.md b/docs/snapshots/v4.23.0/platform_adapters/adapter_interface.md new file mode 100644 index 0000000..ca0b211 --- /dev/null +++ b/docs/snapshots/v4.23.0/platform_adapters/adapter_interface.md @@ -0,0 +1,374 @@ +# Platform Adapter + +平台适配器将外部消息平台接入 AstrBot。插件可注册自定义适配器。 + +## 注册适配器 + +`@register_platform_adapter(adapter_name="id", desc="描述", default_config_tmpl={"key": "value"}, adapter_display_name="显示名", logo_path="logo.png", support_streaming_message=True)` + +## Platform 基类 + +继承 `Platform` 并实现以下方法: + +### 必须实现 + +- `run() -> Coroutine`: 异步阻塞方法,启动客户端 SDK 并持续监听消息。 +- `meta() -> PlatformMetadata`: 返回适配器元数据。 +- `send_by_session(session: MessageSession, message_chain: MessageChain)`: 通过会话发送消息。 + +### 可选重写 + +- `terminate()`: 终止平台运行。 +- `get_client() -> object`: 获取平台客户端对象。 +- `webhook_callback(request) -> Any`: 统一 Webhook 回调入口。 + +### 辅助方法 + +- `commit_event(event: AstrMessageEvent)`: 提交事件到事件队列。 +- `unified_webhook() -> bool`: 是否使用统一 Webhook 模式。 +- `get_stats() -> dict`: 获取平台统计信息。 +- `record_error(message: str, traceback_str: str | None)`: 记录错误。 +- `clear_errors()`: 清除错误记录。 + +### 属性 + +- `config: dict`: 平台配置(用户填写的 default_config_tmpl)。 +- `status: PlatformStatus`: 运行状态(PENDING/RUNNING/ERROR/STOPPED)。 +- `errors: list[PlatformError]`: 错误列表。 +- `last_error: PlatformError | None`: 最近错误。 + +## PlatformMetadata + +```python +PlatformMetadata( + name="adapter_id", # 平台类型标识 + description="适配器描述", + id="adapter_id", # 唯一标识符 + default_config_tmpl={}, # 默认配置模板 + adapter_display_name="显示名", # WebUI 显示名称 + logo_path="logo.png", # Logo 路径(相对于插件目录) + support_streaming_message=True, # 是否支持流式消息 + support_proactive_message=True, # 是否支持主动消息 +) +``` + +## AstrBotMessage + +适配器必须填充以下字段: + +```python +AstrBotMessage( + type=MessageType.GROUP_MESSAGE, # GROUP_MESSAGE / FRIEND_MESSAGE / OTHER_MESSAGE + self_id="bot_id", # 机器人 ID + session_id="session_id", # 会话 ID(决定上下文隔离) + message_id="msg_id", # 消息 ID + group=Group(group_id="123"), # 群组信息(私聊为 None) + sender=MessageMember(user_id="uid", nickname="昵称"), + message=[Plain(text="内容")], # 消息链 + message_str="纯文本内容", # 纯文本汇总 + raw_message=original_data, # 原始平台消息 + timestamp=1234567890, # 时间戳 +) +``` + +### 属性 + +- `group_id: str`: 群组 ID(私聊返回空字符串)。 + +## MessageType 枚举 + +- `MessageType.GROUP_MESSAGE`: 群组消息 +- `MessageType.FRIEND_MESSAGE`: 私聊消息 +- `MessageType.OTHER_MESSAGE`: 其他消息 + +## MessageMember + +```python +MessageMember(user_id="uid", nickname="昵称") +``` + +## Group + +```python +Group( + group_id="123", + group_name="群名", + group_avatar="头像URL", + group_owner="群主ID", + group_admins=["admin1", "admin2"], + members=[MessageMember(...)], +) +``` + +## MessageSession + +```python +MessageSession( + platform_name="adapter_id", # 平台 ID + message_type=MessageType.GROUP_MESSAGE, + session_id="session_id", +) +# 字符串格式: "platform_id:message_type:session_id" +``` + +### 方法 + +- `MessageSession.from_str(session_str)`: 从字符串解析。 + +## AstrMessageEvent + +事件基类,平台适配器需继承并实现 `send()` 方法。 + +### 核心属性 + +- `message_str: str`: 纯文本消息。 +- `message_obj: AstrBotMessage`: 完整消息对象。 +- `platform_meta: PlatformMetadata`: 平台元数据。 +- `session: MessageSession`: 会话对象。 +- `unified_msg_origin: str`: UMO(格式: `platform_id:message_type:session_id`)。 +- `session_id: str`: 会话 ID。 +- `role: str`: 用户角色("member" / "admin")。 +- `is_wake: bool`: 是否唤醒。 +- `is_at_or_wake_command: bool`: 是否 At 或唤醒词。 +- `call_llm: bool`: 是否调用 LLM。 + +### 获取信息方法 + +- `get_platform_name() -> str`: 获取平台类型。 +- `get_platform_id() -> str`: 获取平台 ID。 +- `get_message_str() -> str`: 获取消息文本。 +- `get_message_outline() -> str`: 获取消息概要(图片转 `[图片]`)。 +- `get_messages() -> list[BaseMessageComponent]`: 获取消息链。 +- `get_message_type() -> MessageType`: 获取消息类型。 +- `get_session_id() -> str`: 获取会话 ID。 +- `get_group_id() -> str`: 获取群组 ID。 +- `get_self_id() -> str`: 获取机器人 ID。 +- `get_sender_id() -> str`: 获取发送者 ID。 +- `get_sender_name() -> str`: 获取发送者昵称。 +- `is_private_chat() -> bool`: 是否私聊。 +- `is_wake_up() -> bool`: 是否唤醒。 +- `is_admin() -> bool`: 是否管理员。 + +### 消息发送方法 + +- `send(message: MessageChain)`: 发送消息到平台。 +- `send_streaming(generator: AsyncGenerator, use_fallback: bool)`: 发送流式消息。 +- `react(emoji: str)`: 添加表情回应。 +- `get_group(group_id: str | None) -> Group | None`: 获取群组数据。 + +### 结果设置方法 + +- `set_result(result: MessageEventResult | str)`: 设置事件结果。 +- `stop_event()`: 终止事件传播。 +- `continue_event()`: 继续事件传播。 +- `is_stopped() -> bool`: 是否已终止。 +- `should_call_llm(call_llm: bool)`: 是否调用 LLM。 +- `get_result() -> MessageEventResult | None`: 获取结果。 +- `clear_result()`: 清除结果。 + +### 快捷构建结果 + +- `make_result() -> MessageEventResult`: 创建空结果。 +- `plain_result(text: str) -> MessageEventResult`: 文本结果。 +- `image_result(url_or_path: str) -> MessageEventResult`: 图片结果。 +- `chain_result(chain: list) -> MessageEventResult`: 消息链结果。 + +### LLM 请求 + +- `request_llm(prompt: str, func_tool_manager=None, tool_set=None, session_id="", image_urls=None, contexts=None, system_prompt="", conversation=None) -> ProviderRequest`: 创建 LLM 请求。 + +### 额外信息 + +- `set_extra(key, value)`: 设置额外信息。 +- `get_extra(key: str | None, default=None) -> Any`: 获取额外信息。 +- `clear_extra()`: 清除额外信息。 + +## MessageChain + +消息链,用于构建和发送消息。 + +### 构建方法 + +- `message(text: str)`: 添加文本。 +- `at(name: str, qq: str | int)`: 添加 At。 +- `at_all()`: 添加 AtAll。 +- `url_image(url: str)`: 添加网络图片。 +- `file_image(path: str)`: 添加本地图片。 +- `base64_image(base64_str: str)`: 添加 base64 图片。 +- `use_t2i(use_t2i: bool)`: 设置是否使用文本转图片。 + +### 工具方法 + +- `get_plain_text(with_other_comps_mark: bool) -> str`: 获取纯文本。 +- `squash_plain()`: 合并所有 Plain 消息段。 + +## MessageEventResult + +继承 MessageChain,增加事件控制。 + +### 方法 + +- `stop_event()`: 终止事件传播。 +- `continue_event()`: 继续事件传播。 +- `is_stopped() -> bool`: 是否终止。 +- `set_async_stream(stream: AsyncGenerator)`: 设置异步流。 +- `set_result_content_type(typ: ResultContentType)`: 设置结果类型。 +- `is_llm_result() -> bool`: 是否 LLM 结果。 + +## 消息组件 + +### Plain + +`Plain(text="文本内容")` + +### Image + +```python +Image.fromURL("https://example.com/img.jpg") +Image.fromFileSystem("/path/to/image.jpg") +Image.fromBase64("base64_data") +Image.fromBytes(bytes_data) +``` + +- `convert_to_file_path() -> str`: 转换为本地路径。 +- `convert_to_base64() -> str`: 转换为 base64。 +- `register_to_file_service() -> str`: 注册到文件服务。 + +### Record + +```python +Record.fromFileSystem("/path/to/audio.wav") +Record.fromURL("https://example.com/audio.wav") +Record.fromBase64("base64_data") +``` + +- `convert_to_file_path() -> str`: 转换为本地路径。 +- `convert_to_base64() -> str`: 转换为 base64。 +- `register_to_file_service() -> str`: 注册到文件服务。 + +### Video + +```python +Video.fromFileSystem("/path/to/video.mp4") +Video.fromURL("https://example.com/video.mp4") +``` + +- `convert_to_file_path() -> str`: 转换为本地路径。 +- `register_to_file_service() -> str`: 注册到文件服务。 + +### File + +`File(name="文件名", file="/path/to/file", url="https://...")` + +- `get_file(allow_return_url: bool) -> str`: 异步获取文件。 +- `register_to_file_service() -> str`: 注册到文件服务。 + +### At / AtAll + +```python +At(qq="user_id", name="昵称") +AtAll() +``` + +### Reply + +`Reply(id="message_id", chain=[...], sender_id="uid", sender_nickname="昵称", time=timestamp, message_str="文本")` + +### Face + +`Face(id=123)` + +### Node / Nodes + +```python +Node(uin="qq号", name="昵称", content=[Plain("内容")]) +Nodes(nodes=[Node(...), Node(...)]) +``` + +### Forward + +`Forward(id="forward_id")` + +### Poke + +`Poke(type="poke_type")` + +### Json + +`Json(data={"key": "value"})` + +### WechatEmoji + +`WechatEmoji(md5="md5值", md5_len=长度, cdnurl="CDN链接")` + +## 完整示例 + +```python +from astrbot.api.platform import ( + Platform, AstrBotMessage, MessageMember, MessageType, + PlatformMetadata, register_platform_adapter +) +from astrbot.api.event import AstrMessageEvent, MessageChain +from astrbot.core.platform.astr_message_event import MessageSesion + +@register_platform_adapter("myplatform", "我的平台适配器", default_config_tmpl={ + "token": "", + "enable": False, +}) +class MyPlatformAdapter(Platform): + def __init__(self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue): + super().__init__(platform_config, event_queue) + self.settings = platform_settings + + def meta(self) -> PlatformMetadata: + return PlatformMetadata(name="myplatform", description="我的平台", id=self.config.get("id", "myplatform")) + + async def run(self): + async def on_message(data): + abm = await self.convert_message(data) + await self.handle_msg(abm) + # 启动客户端监听... + + async def convert_message(self, data: dict) -> AstrBotMessage: + abm = AstrBotMessage() + abm.type = MessageType.GROUP_MESSAGE + abm.session_id = data["session_id"] + abm.message_id = data["message_id"] + abm.sender = MessageMember(user_id=data["user_id"], nickname=data["nickname"]) + abm.message_str = data["content"] + abm.message = [Plain(text=data["content"])] + abm.raw_message = data + return abm + + async def handle_msg(self, message: AstrBotMessage): + event = MyPlatformEvent( + message_str=message.message_str, + message_obj=message, + platform_meta=self.meta(), + session_id=message.session_id, + client=self.client, + ) + self.commit_event(event) + + async def send_by_session(self, session: MessageSesion, message_chain: MessageChain): + # 实现发送逻辑... + await super().send_by_session(session, message_chain) + +class MyPlatformEvent(AstrMessageEvent): + def __init__(self, message_str, message_obj, platform_meta, session_id, client): + super().__init__(message_str, message_obj, platform_meta, session_id) + self.client = client + + async def send(self, message: MessageChain): + for comp in message.chain: + if isinstance(comp, Plain): + await self.client.send_text(self.get_sender_id(), comp.text) + await super().send(message) +``` + +## 注意事项 + +- `run()` 必须是阻塞方法,持续监听消息。 +- `convert_message()` 必须正确设置 `session_id`,它决定 LLM 上下文隔离。 +- `commit_event()` 用于提交事件到队列,不可遗漏。 +- 事件类必须实现 `send()` 方法,并在最后调用 `await super().send(message)`。 diff --git a/docs/snapshots/v4.23.0/platform_adapters/message_conversion.md b/docs/snapshots/v4.23.0/platform_adapters/message_conversion.md new file mode 100644 index 0000000..4d56d3c --- /dev/null +++ b/docs/snapshots/v4.23.0/platform_adapters/message_conversion.md @@ -0,0 +1,29 @@ +--- +category: platform_adapters +--- + +# 消息转换逻辑 (Message Conversion) + +`convert_message` 是适配器中最关键的方法,它负责将平台原始的消息格式映射到 AstrBot 的统一模型。 + +### 转换要求 + +在 `convert_message` 中,必须填充 `AstrBotMessage` 的以下核心字段: + +1. **`type`**: 识别是 `GROUP_MESSAGE` 还是 `FRIEND_MESSAGE`。 +2. **`session_id`**: 设置会话隔离。 +3. **`message_str`**: 提取纯文本内容。 +4. **`message`**: 将平台各段消息(如图片、表情)映射为 AstrBot 的 `MessageComponent` 列表。 +5. **`sender`**: 提取发送者的 ID 和昵称。 +6. **`raw_message`**: 保存原始对象。 + +### 提交事件 + +转换完成后,需将其封装为 `AstrMessageEvent` 并提交: + +```python +async def handle_raw_message(self, data): + bot_msg = self.convert_message(data) + event = AstrMessageEvent(bot_msg, self) # 或子类 + self.commit_event(event) +``` diff --git a/docs/snapshots/v4.23.0/platform_adapters/telegram_media_group.md b/docs/snapshots/v4.23.0/platform_adapters/telegram_media_group.md new file mode 100644 index 0000000..823c09e --- /dev/null +++ b/docs/snapshots/v4.23.0/platform_adapters/telegram_media_group.md @@ -0,0 +1,40 @@ +--- +title: Telegram 媒体组处理机制 (Telegram Media Group Handling) +type: feature +status: stable +last_updated: 2025-02-08 +related_base: platform_adapters/adapter_interface.md +--- + +## 概述 + +Telegram 平台在发送包含多张图片或视频的“相册”(Media Group)时,会将其拆分为多个独立的 Update 发送。AstrBot 的 Telegram 适配器实现了缓存与防抖机制,将这些碎片化的消息合并为单个 `AstrMessageEvent`,从而保证插件逻辑的一致性。 + +## 核心逻辑与参数 + +### 1. 收集与防抖机制 +适配器通过 `media_group_id` 识别属于同一相册的消息,并使用 `APScheduler` 进行异步调度处理: +- **`telegram_media_group_timeout` (默认 2.5s)**: 防抖延迟。每收到该组内的一条新消息,计时器都会重置。这是收集所有媒体项的窗口期。 +- **`telegram_media_group_max_wait` (默认 10.0s)**: 硬性超时上限。防止因消息流持续不断导致的无限延迟,达到此时间后将强制触发合并处理。 + +### 2. 消息合并策略 +在 `process_media_group` 方法中,系统执行以下合并逻辑: +- **基础元数据**: 以媒体组的第一条消息作为基准,保留其 `message_str`(通常是相册的 Caption)、回复关系(Reply Chain)和会话上下文。 +- **组件聚合**: 遍历组内所有后续消息,调用 `convert_message` 提取其媒体组件(如 `Image`, `Video`, `File`),并将其 `extend` 到基准消息的 `message` 列表(MessageChain)中。 +- **事件分发**: 合并完成后,仅提交一个封装了完整 `MessageChain` 的 `AstrMessageEvent` 到事件循环。 + +## 关键方法签名 + +- `handle_media_group_message(update, context)`: 拦截带有 `media_group_id` 的消息并管理缓存与调度任务。 +- `process_media_group(media_group_id)`: 核心合并函数,负责从缓存提取数据、重组 `AstrBotMessage` 并触发 `handle_msg`。 + +## 变更影响分析 + +- **插件开发者**: + - **事件密度变化**: 针对 Telegram 平台的相册消息,插件现在只会接收到一个 `AstrMessageEvent`。开发者应预期 `event.message` 列表中可能包含多个 `Image` 或 `Video` 组件。 + - **响应延迟**: 处理 Telegram 相册消息时会有至少 2.5s 的固有延迟,这是为了确保媒体收集完整,属于预期行为。 +- **适配器开发者**: + - 此机制展示了处理“流式/碎片化”平台消息的标准范式:`缓存 -> 防抖调度 -> 组件合并 -> 统一分发`。在接入类似具有媒体组概念的平台(如 Discord)时应参考此实现。 +- **边界情况**: + - 如果相册中的不同图片带有不同的文字说明(虽然 Telegram UI 通常只允许一个 Caption),目前逻辑仅保留第一条消息的文本。 + - 超过 `max_wait` 后到达的消息将被视为独立消息处理。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/plugin_config/command_management.md b/docs/snapshots/v4.23.0/plugin_config/command_management.md new file mode 100644 index 0000000..912cf14 --- /dev/null +++ b/docs/snapshots/v4.23.0/plugin_config/command_management.md @@ -0,0 +1,34 @@ +--- +title: 动态指令管理与权限覆盖 (Dynamic Command Management) +type: feature +status: stable +last_updated: 2024-05-22 +related_base: plugin_config/decorators.md +--- + +## 概述 +AstrBot 引入了动态指令管理机制,允许在运行时通过 Dashboard 或 API 修改指令的元数据(如名称、启用状态)和执行权限。这意味着插件代码中通过装饰器(如 `@filter.permission_type`)定义的静态配置现在仅作为“初始默认值”,系统支持持久化的运行时覆盖。 + +## 核心逻辑与 API + +### 1. 权限动态更新 (`update_command_permission`) +该服务函数负责修改特定指令的处理权限。其核心流程如下: +- **定位处理函数**:通过 `handler_full_name` 检索指令描述符。 +- **持久化配置**:将权限变更写入全局 KV 存储中的 `alter_cmd` 字典。存储结构为 `alter_cmd -> {plugin_name} -> {handler_name} -> { "permission": "admin" | "member" }`。 +- **运行时滤镜注入**: + - 遍历指令关联的 `event_filters`。 + - 如果存在 `PermissionTypeFilter`,则直接更新其 `permission_type` 属性。 + - 如果不存在,则在滤镜列表首位插入一个新的 `PermissionTypeFilter` 实例。 + +### 2. 权限类型映射 +- `admin`: 映射为 `PermissionType.ADMIN`。 +- `member`: 映射为 `PermissionType.MEMBER`。 + +## 数据流向 +1. **配置加载**:插件加载时,系统会读取 `alter_cmd` 中的持久化设置并应用到指令实例。 +2. **实时生效**:通过 Dashboard 修改权限后,后端会同步更新内存中的 `handler.event_filters`,无需重启插件即可生效。 + +## 变更影响分析 +- **权限判定优先级**:动态配置(`alter_cmd`) > 装饰器静态定义。AI 开发者在调试权限问题时,应优先检查全局配置而非仅查看源码中的 `@filter.permission_type`。 +- **滤镜链可变性**:指令的 `event_filters` 列表现在是动态可变的。插件开发者不应假设滤镜列表在插件生命周期内保持不变。 +- **副作用**:修改权限会直接影响 `AstrMessageEvent` 的分发逻辑。如果一个指令被动态设为 `ADMIN`,则非管理员发送的消息将在 `PermissionTypeFilter` 阶段被拦截,不会进入插件业务逻辑。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/plugin_config/decorators.md b/docs/snapshots/v4.23.0/plugin_config/decorators.md new file mode 100644 index 0000000..489dbb0 --- /dev/null +++ b/docs/snapshots/v4.23.0/plugin_config/decorators.md @@ -0,0 +1,30 @@ +--- +category: plugin_config +--- + +# 常用装饰器 (Decorators) + +AstrBot 提供了一系列基于 `astrbot.api.event.filter` 的装饰器,用于注册插件和控制消息处理逻辑。 + +### 注册装饰器 + +- **`@register(id, author, description, version, repo_url)`** + - 标记插件类,提供基础元数据(若存在 `metadata.yaml` 则优先级较低)。 + +### 消息过滤器装饰器 + +过滤器遵循 **AND 逻辑**,即所有条件均满足时才触发。 + +| 装饰器 | 参数说明 | +| :--- | :--- | +| `@filter.command(name, alias, priority)` | 注册指令。支持带参函数,如 `def add(self, event, a: int, b: int)`。 | +| `@filter.command_group(name)` | 注册指令组。子指令通过 `@组名.command` 注册。 | +| `@filter.event_message_type(type)` | 过滤消息来源类型(`ALL`, `PRIVATE_MESSAGE`, `GROUP_MESSAGE`)。 | +| `@filter.platform_adapter_type(type)` | 过滤平台类型(`AIOCQHTTP`, `TELEGRAM` 等)。支持按位或 `|`。 | +| `@filter.permission_type(type)` | 校验权限(如 `ADMIN`)。 | +| `@filter.regex(pattern)` | 正则表达式匹配。 | + +### 注意事项 + +- 指令名不应包含空格。 +- 优先级 `priority` 默认为 0,数值越大优先级越高。 diff --git a/docs/snapshots/v4.23.0/plugin_config/file_config.md b/docs/snapshots/v4.23.0/plugin_config/file_config.md new file mode 100644 index 0000000..8337071 --- /dev/null +++ b/docs/snapshots/v4.23.0/plugin_config/file_config.md @@ -0,0 +1,30 @@ +--- +title: 插件文件配置系统 (Plugin File Config) +type: feature +status: stable +last_updated: 2024-05-22 +related_base: plugin_config/schema.md +--- + +## 概述 +AstrBot 引入了原生的文件配置支持,允许插件开发者在 `_conf_schema.json` 中定义文件类型的配置项。该系统集成了 Dashboard 文件上传、路径安全清洗及自动化存储管理,使插件能够以标准化的方式管理模型权重、静态资源或本地数据库文件。 + +## Schema 定义 +在插件的 `_conf_schema.json` 中,可以通过以下字段定义文件配置项: +- **`type`**: 必须设置为 `"file"`。 +- **`file_types`**: (可选) 字符串数组,定义允许的文件后缀名白名单(例如 `[".jpg", ".png", ".onnx"]`)。 +- **`description`**: 配置项描述,将显示在 Dashboard 的上传控件旁。 + +## 存储与路径逻辑 +1. **物理存储**: 上传的文件统一存储在 `data/plugins//files//` 目录下。其中 `` 是配置项在 Schema 中的点号路径(dot-path),确保了不同配置项间的文件隔离。 +2. **路径安全**: 系统通过 `sanitize_filename` 强制清洗文件名以防止路径穿越攻击,并使用 `normalize_rel_path` 确保跨平台路径的一致性。 +3. **配置引用**: 存入插件 `config.json` 的值为相对于插件数据目录的规范化路径(始终以 `files/` 开头)。 + +## 核心校验机制 +- **`validate_config`**: 在配置保存前,核心系统会强制执行校验逻辑,确保所有 `file` 类型的路径均指向合法的插件内部存储区域,并符合后缀名白名单。 +- **`MAX_FILE_BYTES`**: 系统级限制上传文件大小,默认为 500MB。 + +## 变更影响分析 +- **资源管理标准化**: 开发者不再需要手动编写文件上传接口或处理复杂的 `multipart/form-data` 逻辑,只需通过 `self.config` 即可获取已就绪的本地文件路径。 +- **安全性增强**: 统一的路径清洗和校验机制消除了插件开发者自行处理文件路径时可能引入的任意文件读写漏洞。 +- **AI 开发者适配**: 在为 AstrBot 编写插件 Schema 时,AI 助手应优先推荐使用 `type: "file"` 来处理外部资源依赖,而非要求用户手动填写绝对路径。注意,保存配置时必须通过 `validate_config` 校验,否则配置将无法持久化。 \ No newline at end of file diff --git a/docs/snapshots/v4.23.0/plugin_config/hooks.md b/docs/snapshots/v4.23.0/plugin_config/hooks.md new file mode 100644 index 0000000..0ae6d6b --- /dev/null +++ b/docs/snapshots/v4.23.0/plugin_config/hooks.md @@ -0,0 +1,93 @@ +--- +category: plugin_config +--- + +# 事件钩子 (Hooks) + +事件钩子用于在 AstrBot 核心执行流程的关键节点介入(例如:LLM 请求前后、工具调用前后、发送消息前后)。 + +> 事件钩子是“插件事件系统”的一部分,和 Agent 运行钩子(`BaseAgentRunHooks`)不是同一套机制。 +> Agent 运行钩子见:`docs/agent/agent-related-hooks.md` + +## 使用方式 + +```python +from astrbot.api.event import filter, AstrMessageEvent + +@filter.on_astrbot_loaded() +async def on_loaded(self): + ... +``` + +### 重要限制与最佳实践 + +- 事件钩子通常不支持与指令/过滤器混用(例如:`@filter.command`、`@filter.command_group`、`@filter.event_message_type`、`@filter.platform_adapter_type`、`@filter.permission_type`)。 +- 大多数事件钩子不建议用 `yield` 发送消息:如果要发消息,请直接调用 `await event.send(...)`。 +- 事件钩子应当短小、幂等、可失败(失败只影响当前 hook,不应导致整个机器人崩溃)。 + +## 核心钩子清单 + +下面列出对外暴露的常用事件钩子(按执行流程排序)。签名写法以核心实现为准(参考源码:`astrbotcore/astrbot/core/star/register/star_handler.py`)。 + +### 1) 生命周期 + +- `@filter.on_astrbot_loaded()` + - 触发:AstrBot 加载完成 + - 建议签名:`async def on_astrbot_loaded(self) -> None` + +- `@filter.on_platform_loaded()` + - 触发:平台加载完成 + - 建议签名:`async def on_platform_loaded(self) -> None` + +### 2) LLM 请求前后 + +- `@filter.on_waiting_llm_request()` + - 触发:确定要调用 LLM,但尚未获取会话锁之前(适合提示“思考中/排队中”) + - 建议签名:`async def on_waiting_llm(self, event: AstrMessageEvent) -> None` + +- `@filter.on_llm_request()` + - 触发:LLM 请求发送前(可修改请求内容) + - 建议签名:`async def on_llm_request(self, event: AstrMessageEvent, request: ProviderRequest) -> None` + - 常见用途: + - 注入/调整 `system_prompt` + - 根据平台/会话动态切换模型或工具策略(请保持可解释、可回滚) + +- `@filter.on_llm_response()` + - 触发:LLM 请求完成后(可读取/修饰返回结果) + - 建议签名:`async def on_llm_response(self, event: AstrMessageEvent, response: LLMResponse) -> None` + +### 3) 工具调用前后(Function Calling / Tools Use) + +- `@filter.on_using_llm_tool()` + - 触发:函数工具调用前 + - 建议签名:`async def on_using_tool(self, event: AstrMessageEvent, tool: FunctionTool, tool_args: dict | None) -> None` + - 常见用途: + - 记录审计日志 / 埋点 + - 根据会话状态拒绝某些工具(需配合工具层做硬限制) + +- `@filter.on_llm_tool_respond()` + - 触发:函数工具调用后 + - 建议签名:`async def on_tool_respond(self, event: AstrMessageEvent, tool: FunctionTool, tool_args: dict | None, tool_result: CallToolResult | None) -> None` + - 常见用途: + - 对工具结果做脱敏/裁剪 + - 追加提示,让 LLM 更好地总结工具输出 + +### 4) 发送消息前后 + +- `@filter.on_decorating_result()` + - 触发:发送消息前(用于“装饰”即将发送的消息链) + - 建议签名:`async def on_decorating_result(self, event: AstrMessageEvent) -> None` + - 常见用途: + - 文转图、追加后缀、统一格式化输出 + - 注意:这里的职责是“改 `event.get_result().chain`”,不是发消息;如需主动发送请使用 `event.send()`。 + +- `@filter.after_message_sent()` + - 触发:消息成功发送到平台后 + - 建议签名:`async def after_message_sent(self, event: AstrMessageEvent) -> None` + +## 相关文档与源码 + +- Agent 运行钩子:`docs/agent/agent-related-hooks.md` +- `filter` 对外导出位置:`astrbotcore/astrbot/api/event/filter/__init__.py` +- Hook 注册实现:`astrbotcore/astrbot/core/star/register/star_handler.py` +- 事件类型枚举:`astrbotcore/astrbot/core/star/star_handler.py` diff --git a/docs/snapshots/v4.23.0/plugin_config/lifecycle.md b/docs/snapshots/v4.23.0/plugin_config/lifecycle.md new file mode 100644 index 0000000..67807d3 --- /dev/null +++ b/docs/snapshots/v4.23.0/plugin_config/lifecycle.md @@ -0,0 +1,32 @@ +--- +category: plugin_config +--- + +# 插件生命周期 (Lifecycle) + +AstrBot 的插件系统基于**运行时注入**和**动态加载**机制。 + +### 插件结构规范 + +一个标准的 AstrBot 插件(Star)通常包含: +- `main.py`: 插件入口,包含继承自 `Star` 的类。 +- `metadata.yaml`: 插件元数据(ID、名称、版本、作者等)。 +- `_conf_schema.json`: 可选,配置 Schema。 +- `requirements.txt`: 可选,依赖定义。 +- `logo.png`: 可选,图标。 + +### 加载流程 + +1. **扫描**: 扫描 `data/plugins` 目录。 +2. **解析**: 读取元数据。 +3. **依赖校验**: 检查并提示缺失依赖。 +4. **实例化**: + - 解析 `_conf_schema.json` 并加载配置实体 `AstrBotConfig`。 + - 实例化插件类,注入 `Context` 和 `AstrBotConfig`。 +5. **注册**: 扫描 `@filter` 装饰器方法并注册到事件分发中心。 + +### 卸载与重载 + +- 调用插件实例的 `terminate()` 异步方法。 +- 清除事件分发中心中该插件的所有 Handler。 +- 允许在不重启 Bot 的情况下动态重载。 diff --git a/docs/snapshots/v4.23.0/plugin_config/schema.md b/docs/snapshots/v4.23.0/plugin_config/schema.md new file mode 100644 index 0000000..9daca24 --- /dev/null +++ b/docs/snapshots/v4.23.0/plugin_config/schema.md @@ -0,0 +1,81 @@ +--- +category: plugin_config +--- + +# 配置 Schema (`_conf_schema.json`) + +AstrBot 通过 Schema 实现配置的自动解析与 WebUI 可视化渲染。 + +### 配置定义 + +在插件目录下添加 `_conf_schema.json` 文件,定义配置项的 Schema。 + +### 字段说明 + +| 字段名 | 说明 | +| :--- | :--- | +| `type` | **必填**。支持 `string`, `text`, `int`, `float`, `bool`, `object`, `list`, `dict`, `template_list` | +| `description` | 配置描述 | +| `hint` | 提示语,右侧问号图标悬浮显示 | +| `obvious_hint` | 是否醒目显示 | +| `default` | 默认值 | +| `options` | 下拉列表可选项 | +| `items` | `object` 类型的子 Schema | +| `editor_mode` | 启用代码编辑器 (Monaco Editor) | +| `editor_language` | 代码编辑器语言,默认 `json` | +| `editor_theme` | 代码编辑器主题,`vs-light` 或 `vs-dark` | +| `_special` | 调用内置数据:`select_provider`, `select_provider_tts`, `select_provider_stt`, `select_persona` | +| `invisible` | 是否隐藏,默认 `false` | + +### 高级类型 + +- **`text`**: 多行文本输入 +- **`dict`**: 键值对配置,支持 `template_schema` 定义子项 +- **`template_list`**: 多组重复配置(v4.10.4+) + +### `template_list` 类型 + +用于保存多组重复配置,如多个 API 供应商或多套人设。 + +```json +{ + "providers": { + "type": "template_list", + "description": "API 供应商列表", + "templates": { + "openai": { + "name": "OpenAI", + "items": { + "api_key": {"description": "API Key", "type": "string", "default": "sk-xxxx"}, + "model": {"description": "模型名称", "type": "string", "default": "gpt-3.5-turbo"} + } + } + } + } +} +``` + +存储格式(包含 `__template_key` 字段): + +```json +{ + "providers": [ + {"__template_key": "openai", "api_key": "sk-xxxx", "model": "gpt-3.5-turbo"} + ] +} +``` + +### 在插件中使用 + +```python +from astrbot.api import AstrBotConfig + +@register("config", "Soulter", "一个配置示例", "1.0.0") +class ConfigPlugin(Star): + def __init__(self, context: Context, config: AstrBotConfig): + super().__init__(context) + self.config = config + # self.config.save_config() # 保存配置 +``` + +配置更新时,AstrBot 会自动添加缺失的默认值、移除不存在的配置项。 diff --git a/docs/snapshots/v4.23.0/plugin_config/session_control.md b/docs/snapshots/v4.23.0/plugin_config/session_control.md new file mode 100644 index 0000000..bdf93f3 --- /dev/null +++ b/docs/snapshots/v4.23.0/plugin_config/session_control.md @@ -0,0 +1,31 @@ +--- +category: plugin_config +--- + +# 会话控制 (Session Control) + +`session_waiter` 是实现多轮对话状态机的核心机制,常用于问答、验证或连续操作。 + +### 核心装饰器 + +- **`@session_waiter(timeout: float, record_history_chains: bool = False)`** + - 用于定义一个等待用户进一步输入的异步函数。 + - 超时会抛出 `TimeoutError`。 + +### SessionController 接口 + +Waiter 函数通常接收一个 `controller: SessionController` 参数: + +- `keep(timeout, reset_timeout)`: 保持会话拦截。`reset_timeout=True` 将重置计时。 +- `stop()`: 立即终止拦截,恢复正常消息分发。 +- `get_history_chains()`: 获取拦截期间的消息历史。 + +### SessionFilter (自定义会话隔离) + +通过继承 `SessionFilter` 并重写 `filter` 方法,可以自定义拦截的范围(如按群组拦截): + +```python +class GroupFilter(SessionFilter): + def filter(self, event: AstrMessageEvent) -> str: + return event.get_group_id() or event.unified_msg_origin +``` diff --git a/scripts/state.json b/scripts/state.json index 5a19011..e46264b 100644 --- a/scripts/state.json +++ b/scripts/state.json @@ -1,4 +1,4 @@ { - "last_commit_sha": "06fd2d2428dd1333eca30447f6c5329fa7686144", - "last_tag": "v4.20.0" + "last_commit_sha": "61313868939119524fa88094accc0c0b77dad4aa", + "last_tag": "v4.23.0" } \ No newline at end of file