FaustBot 插件系统 API Reference

本文档完整描述 FaustBot 插件系统的架构、Hook 接口、数据结构和最佳实践。

目录


架构概览

FaustBot 插件系统基于 pluggy 框架实现,支持两种插件风格:

风格 基类 特点
新风格 FaustPlugin 继承基类,自动注册 @hookimpl,推荐使用
旧风格 自定义 Plugin 手动实现 on_load/on_unload,兼容旧插件

插件加载流程:

  1. PluginManager 扫描 ~/.faustbot/plugins/ 目录
  2. 读取 plugin.json 清单
  3. 动态导入入口文件(importlib
  4. 调用 get_plugin() 获取插件实例
  5. 注册到 pluggy 管理器
  6. 依次调用 plugin_loadedstartup 等生命周期 Hook

插件目录结构

~/.faustbot/plugins/my-plugin/
├── plugin.json              # 插件元数据必需
├── impl.py                  # 入口文件推荐
├── data/                    # 插件数据目录可选
   └── data.db
└── frontend/                # 前端资源可选
    ├── panel.js
    ├── app-hook.js
    └── panel.css

plugin.json 字段

{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "插件功能描述",
  "author": "FaustBot",
  "homepage": "https://github.com/...",
  "enabled": true,
  "entry": "impl.py",
  "permissions": ["tool:myTool", "middleware:myMiddleware"],
  "priority": 500
}
字段 类型 必需 默认值 说明
id string - 插件唯一标识符
name string - 显示名称
version string "0.1.0" 语义化版本号
description string "" 功能描述
author string "" 作者
homepage string "" 项目主页
enabled bool true 是否启用
entry string "main.py" 入口文件名
permissions list [] 所需权限声明
priority int 100 加载优先级(越小越先)

PluginContext API

PluginContext 是每个 Hook 方法接收的上下文对象,提供以下能力:

属性

属性 类型 说明
plugin_id str 插件 ID
plugin_dir Path 插件安装目录
plugin_data_dir Path \| None 插件数据目录(data/ 子目录)

触发器管理

# 创建触发器
ctx.trigger_create({
    "id": "my_event::1234567890",
    "type": "event",
    "event_name": "my_event",
    "payload": {"summary": "事件摘要"},
    "recall_description": "Agent 可读的描述",
    "lifespan": 7200,
})

# 列出所有触发器
triggers = ctx.trigger_list()

# 获取单个触发器
trigger = ctx.trigger_get("my_event::1234567890")

# 更新触发器
ctx.trigger_update("my_event::1234567890", {"payload": {"summary": "新摘要"}})

# 删除触发器
ctx.trigger_delete("my_event::1234567890")

配置管理

# 注册配置 schema(list 格式,推荐)
ctx.register_config([
    {"key": "PUSH_THRESHOLD", "type": "int", "label": "推送阈值", "default": 3},
    {"key": "QUIET_START", "type": "str", "label": "静默开始", "default": "23:00"},
    {"key": "ENABLE_FEATURE", "type": "bool", "label": "启用功能", "default": True},
])

# 注册配置 schema(string 格式,旧版兼容)
ctx.register_config("""
PUSH_THRESHOLD:int:推送阈值=3
QUIET_START:str:静默开始=23:00
""")

# 读取配置
threshold = ctx.get_config("PUSH_THRESHOLD", 3)

# 写入配置
ctx.set_config("PUSH_THRESHOLD", 5)

# 列出所有配置
configs = ctx.list_configs()

支持的配置类型:str, string, int, float, bool, json, text

虚拟文件系统(VFS)

# 读取文本
content = ctx.vfs_read_text("/plugins/my-plugin/data.md", default="")

# 写入文本
ctx.vfs_write("/plugins/my-plugin/index.md", "# My Plugin\n\n内容...")

# 写入动态内容(符号链接,每次读取时调用函数)
ctx.vfs_write_symbolic(
    "/plugins/my-plugin/dynamic.json",
    lambda path: json.dumps(get_data()),
    should_be_included_in_search=True,
)

# 删除文件
ctx.vfs_delete("/plugins/my-plugin/old.md")

# 列出目录
files = ctx.vfs_list("/plugins/my-plugin/")

Hook 完整参考

生命周期

plugin_loaded(ctx: PluginContext) -> None

插件加载完成后调用。用于初始化全局状态、注册配置等。

@hookimpl
def plugin_loaded(self, ctx: PluginContext) -> None:
    global _PLUGIN
    _PLUGIN = self
    self.ctx = ctx
    self.store = MyStore(ctx.plugin_data_dir or (ctx.plugin_dir / "data"))

plugin_unloaded(ctx: PluginContext) -> None

插件卸载前调用。用于清理资源、关闭连接等。

@hookimpl
def plugin_unloaded(self, ctx: PluginContext) -> None:
    global _PLUGIN
    if _PLUGIN is self:
        _PLUGIN = None

startup(ctx: PluginContext) -> None

插件启动时调用(在 plugin_loaded 之后)。用于执行初始化逻辑。

def startup(self, ctx: PluginContext) -> None:
    self.ctx = ctx
    ctx.register_config([...])
    ctx.vfs_write("/plugins/my-plugin.md", "...")

heartbeat(ctx: PluginContext) -> None

周期性心跳(约每 10 秒)。用于检查状态、触发推送等。

@hookimpl
def heartbeat(self, ctx: PluginContext) -> None:
    pending = self.store.count_pending()
    if pending >= self.threshold:
        self.push_digest()

health_check() -> dict | None

返回健康状态。第一个非 None 结果生效。

@hookimpl
def health_check(self) -> dict | None:
    return {
        "status": "ok",
        "plugin": "my-plugin",
        "items": len(self.store.list_items()),
    }

前端

register_frontend() -> list[dict]

声明前端 JS/CSS 资源。路径会被挂载到 /faust/plugins/{plugin_id}/frontend/

@hookimpl
def register_frontend(self) -> list[dict]:
    return [
        {"type": "js", "path": "/faust/plugins/my-plugin/frontend/panel.js"},
        {"type": "js", "path": "/faust/plugins/my-plugin/frontend/app-hook.js"},
        {"type": "css", "path": "/faust/plugins/my-plugin/frontend/panel.css"},
    ]

communicate_handler(payload: dict, ctx: PluginContext) -> dict | None

处理前端 POST 请求。详见 前后端通信

@hookimpl
def communicate_handler(self, payload: dict, ctx: PluginContext) -> dict | None:
    action = str((payload or {}).get("action") or "").strip().lower()
    if action == "get_state":
        return {"status": "ok", "state": self.get_state()}
    return {"status": "error", "detail": f"unknown action: {action}"}

定时任务

register_schedules() -> list[dict]

注册定时任务。支持 interval(秒)或 cron 表达式。

@hookimpl
def register_schedules(self) -> list[dict]:
    return [
        {
            "id": "my-plugin-fetch",
            "interval": 300,
            "callback": self.fetch_data,
            "description": "每5分钟拉取数据",
        },
    ]

async def fetch_data(self):
    # 异步任务
    pass

依赖

register_pip_deps() -> list[str]

声明 pip 依赖包。插件加载时自动安装。

@hookimpl
def register_pip_deps(self) -> list[str]:
    return ["pandas>=2.0", "httpx"]

工具与中间件

register_tools(ctx: PluginContext) -> list

注册 Agent 可用工具。

from langchain.tools import tool
from faust_backend.plugin_system import ToolSpec

@tool
def my_search(query: str) -> str:
    """搜索指定内容。"""
    return f"搜索结果: {query}"

@hookimpl
def register_tools(self, ctx: PluginContext) -> list:
    return [
        ToolSpec(
            name="mySearch",
            tool=my_search,
            enabled_by_default=True,
            description="自定义搜索工具",
        ),
    ]

register_middlewares(ctx: PluginContext) -> list

注册 Agent 中间件。

from faust_backend.plugin_system import MiddlewareSpec

@hookimpl
def register_middlewares(self, ctx: PluginContext) -> list:
    return [
        MiddlewareSpec(
            name="my_middleware",
            middleware=MyMiddlewareClass(),
            priority=260,
            enabled_by_default=True,
            description="上下文裁剪中间件",
        ),
    ]

tool_call_pre(name: str, args: dict, ctx: PluginContext) -> dict | None

工具调用前拦截。返回修改后的 args 或 None 阻止调用。

@hookimpl
def tool_call_pre(self, name: str, args: dict, ctx: PluginContext) -> dict | None:
    if name == "execute" and "dangerous" in args.get("command", ""):
        return None  # 阻止危险命令
    return args

tool_call_post(name: str, args: dict, result: Any, ctx: PluginContext) -> Any

工具调用后处理。可修改返回结果。

@hookimpl
def tool_call_post(self, name: str, args: dict, result: Any, ctx: PluginContext) -> Any:
    if name == "read":
        return f"[已缓存] {result}"
    return None  # 不修改

消息

message_received(msg: Any, history: list, ctx: PluginContext) -> str | None

拦截/修改用户消息。返回 None 不修改。

@hookimpl
def message_received(self, msg: Any, history: list, ctx: PluginContext) -> str | None:
    # 记录用户活动时间
    self.last_user_activity_ts = time.time()
    return None  # 不修改消息

agent_event_sent(event: dict, current_history: list, ctx: PluginContext) -> dict | None

Agent 事件发送到前端前拦截。返回 None 可抑制事件。

@hookimpl
def agent_event_sent(self, event: dict, current_history: list, ctx: PluginContext) -> dict | None:
    # 抑制 EmotionInvoke 工具事件
    if event.get("tool_name") == "EmotionInvoke":
        return None
    return None  # 不修改

记忆

memory_read_pre(query: str, filters: dict | None, ctx: PluginContext) -> str | None

记忆读取前重写查询。

@hookimpl
def memory_read_pre(self, query: str, filters: dict | None, ctx: PluginContext) -> str | None:
    # 追加关键词
    return f"{query} 最近更新"

memory_read_post(query: str, results: list, ctx: PluginContext) -> list | None

记忆读取后重排/过滤结果。

@hookimpl
def memory_read_post(self, query: str, results: list, ctx: PluginContext) -> list | None:
    # 只保留前5条
    return results[:5]

memory_write_pre(content: str, metadata: dict | None, ctx: PluginContext) -> str | None

记忆写入前拦截/修改内容。

@hookimpl
def memory_write_pre(self, content: str, metadata: dict | None, ctx: PluginContext) -> str | None:
    # 追加标签
    if metadata is None:
        metadata = {}
    metadata["tags"] = ["my-plugin"]
    return content

memory_write_post(content: str, metadata: dict | None, id: str, ctx: PluginContext) -> None

记忆写入后通知。

@hookimpl
def memory_write_post(self, content: str, metadata: dict | None, id: str, ctx: PluginContext) -> None:
    log.info("记忆已写入: %s", id)

触发器

trigger_append(payload: dict, ctx: PluginContext) -> dict | None

触发器追加前过滤/修改。

@hookimpl
def trigger_append(self, payload: dict, ctx: PluginContext) -> dict | None:
    # 过滤特定类型
    if payload.get("type") == "blocked":
        return None
    return payload

trigger_fire(payload: dict, ctx: PluginContext) -> dict | None

触发器触发前过滤/修改。

@hookimpl
def trigger_fire(self, payload: dict, ctx: PluginContext) -> dict | None:
    # 修改触发内容
    payload["payload"]["summary"] = f"[已审核] {payload['payload'].get('summary', '')}"
    return payload

Prompt

register_prompt_suffix() -> list[str]

注入 Agent 系统提示后缀。

@hookimpl
def register_prompt_suffix(self) -> list[str]:
    return [
        "\n[My Plugin]\n"
        "本插件提供 XXX 功能。\n"
        "当用户请求 XXX 时,请使用 mySearch 工具。\n"
    ]

配置

config_changed(key: str, old: Any, new: Any, ctx: PluginContext) -> None

配置值变更时调用。

@hookimpl
def config_changed(self, key: str, old: Any, new: Any, ctx: PluginContext) -> None:
    if key == "PUSH_THRESHOLD":
        self.threshold = int(new or 3)

数据结构

ToolSpec

@dataclass
class ToolSpec:
    name: str                    # 工具名称
    tool: Any                    # 工具函数或 StructuredTool
    enabled_by_default: bool = True
    description: str = ""

MiddlewareSpec

@dataclass
class MiddlewareSpec:
    name: str                    # 中间件名称
    middleware: Any              # 中间件实例
    priority: int = 100          # 优先级(越小越先执行)
    enabled_by_default: bool = True
    description: str = ""

PluginManifest

@dataclass
class PluginManifest:
    plugin_id: str
    name: str
    version: str = "0.1.0"
    description: str = ""
    author: str = ""
    homepage: str = ""
    enabled: bool = True
    entry: str = "main.py"
    permissions: list[str] = field(default_factory=list)
    priority: int = 100

前后端通信

后端接口

POST /faust/plugins/{plugin_id}/communicate

请求体:JSON 对象,通常包含 action 字段。

响应:JSON 对象,推荐统一格式:

{"status": "ok", ...}

或错误:

{"status": "error", "detail": "错误描述"}

错误语义:

HTTP 状态码 说明
404 插件不存在
503 插件禁用或未加载
400 插件未实现 communicate_handler

前端调用

配置窗口插件页面:

const result = await window.pluginUI.communicate('my-plugin', {
  action: 'get_state'
});

主前端插件脚本:

const result = await window.faustAppUI.communicate('my-plugin', {
  action: 'get_context'
});

设计约束

  • 不要通过 register_routes 注册插件 Router(已废弃)
  • 所有前后端交互都收敛到 communicate 接口
  • 插件前端静态资源仍通过 /faust/plugins/{plugin_id}/frontend/... 提供

Admin API

方法 端点 说明
GET /faust/admin/plugins 列出所有插件
POST /faust/admin/plugins/reload 强制重载所有插件
POST /faust/admin/plugins/heartbeat 触发一次心跳
POST /faust/admin/plugins/{id}/enable 启用插件
POST /faust/admin/plugins/{id}/disable 禁用插件
GET /faust/admin/plugins/{id}/config 获取插件配置
POST /faust/admin/plugins/{id}/config 设置插件配置
POST /faust/plugins/{id}/communicate 插件通信
GET /faust/admin/plugin-market/catalog 获取插件市场目录
POST /faust/admin/plugin-market/install 从市场安装
POST /faust/admin/plugins/install-zip 从 ZIP 安装
POST /faust/admin/plugins/package-zip 打包插件为 ZIP
DELETE /faust/admin/plugins/{id} 删除插件

默认插件示例

rss-watcher(新风格,完整示例)

RSS 订阅监控、摘要生成、横幅注入。

关键模式: - SQLite 存储 - 定时抓取(register_schedules) - 心跳推送(heartbeat) - VFS 索引写入 - 触发器创建 - 记忆集成

emotion-engine(新风格,工具示例)

情感状态追踪(6维向量)。

关键模式: - register_tools 注册 EmotionInvoke 工具 - agent_event_sent 抑制工具事件 - register_prompt_suffix 注入情感状态 - communicate_handler 前端通信

desktop-mood(新风格,环境感知示例)

桌面环境感知(CPU/内存/电量/窗口/天气/媒体)。

关键模式: - Windows API 调用 - 规则引擎系统 - communicate_handler 前端通信 - register_prompt_suffix 注入环境上下文