Skip to content

zh plugin certified plugins

langbot-docs-sync[bot] edited this page Sep 29, 2026 · 6 revisions

认证插件与共享运行

认证是什么

认证是 LangBot 对一份精确、不可变的插件制品授予的资格:该制品被批准在 LangBot Cloud 中跨租户共享运行。

  • 认证绑定到制品本身(已发布的具体版本,按 digest 区分),不是绑定到插件名或作者。同一个插件发布新版本后,上一版的认证不会自动延续。
  • 认证不是质量评分,也不是安全审计背书。

为什么需要认证

平台默认不让插件共享运行。这是因为共享的粒度是制品:

  • 专属模式:插件不参与共享,独立运行。
  • 共享模式:同一个 artifact digest 的所有安装共用一个 Worker。

共享的好处是进程数量和启动开销不随安装数线性增长。代价是一个进程里会同时处理多个工作区的调用——如果插件把租户数据放在实例上,就会在租户之间串数据。

所以平台需要先对这份制品做代码审查、确认它满足无状态约束,再用签名把"对这份制品的信任"固定下来,才敢让它进入共享放置。这就是认证存在的原因。

什么情况下需要认证

大多数插件不需要认证。 不认证不影响你把插件发布到插件市场,也不影响用户安装使用——插件会以专属模式正常运行。

你的情况 是否需要认证
只想把插件发布到插件市场供用户使用 不需要
插件需要在进程内保留跨调用状态(例如常驻的外部会话进程) 不需要,也应该保持专属运行
插件里有后台任务、定时器,或长期缓存租户数据 不需要,保持专属模式更安全
希望获得认证插件标识 需要
希望同一制品的多个安装共用一个进程,降低资源与启动开销 需要
插件面向 Cloud 多租户场景,希望被平台以共享方式放置 需要

判断标准只有一条:只有当你的代码确实能做到"实例上不保存任何租户或安装相关数据"时,认证才有意义。

认证之后的效果

通过审核后:

  • 制品被签入证书,安装时以共享模式运行;同一 digest 的多个安装共用一个 Worker。
  • 该 Worker 中只有一个 BasePlugin 实例,以及每个已声明组件 manifest 各一个实例(EventListener、Tool、Command、KnowledgeEngine、Parser、Page、Runner)。
  • 每个安装只额外保留一份轻量的配置、权限与生命周期上下文,调用时由当前请求决定使用哪一份。
  • 你会获得认证插件标识。
  • 自托管 OSS 默认不配置认证签发方公钥,因此即使是带认证的制品,在自托管实例上仍以专属模式运行。

同时你也承担一项长期义务:后续每个版本都必须继续满足无状态约束。修改代码后需要提交新版本重新审核,认证不会自动延续。

需要做的适配与规范

声明

在 manifest.yaml 的 execution 下同时声明两个字段:

apiVersion: v1
kind: Plugin
metadata:
  author: your-name
  name: YourPlugin
  version: 0.1.1
spec:
  config: []
  components: {}
execution:
  python:
    path: main.py
    attr: YourPlugin
  sharedRuntime: shared-runtime-v1
  componentModel: stateless-v1
字段 合法值 说明
execution.sharedRuntime shared-runtime-v1 只接受这一个值,填写其他值会导致清单校验失败
execution.componentModel stateless-v1 只接受这一个值
  • 两个字段必须同时声明。只写 sharedRuntime 不写 componentModel 的包会在认证校验阶段被拒绝。
  • 这两个字段自 SDK 0.7.0 起被识别;更早的 SDK 会忽略它们,因此不要用旧版本验证声明是否生效。

Warning

声明只是申请,不等于通过认证。它本身不会授予认证标识,也不会改变运行模式。

无状态组件规范

  • BasePlugin.initialize() 和组件 initialize() 是进程级初始化,每个 Worker 只执行一次。此时不存在"当前安装",也不存在"当前工作区"。
  • 组件实例会被不同工作区并发调用,因此所有方法都必须可重入。
  • 读取当前安装的配置:BasePlugin 用 self.get_config(),组件用 self.get_plugin_config() 或 self.plugin.get_config(),Runner 可以直接用 ctx.config。
  • 不得把配置、凭据、工作区或安装标识、请求数据保存在实例字段或模块级全局变量中。
  • 不得在 initialize() 中读取并缓存安装配置。
  • 存储、文件、凭据、日志、模型等平台能力必须通过 SDK 的 Host API 访问;权限由当前调用自动解析,不需要也不应该自己缓存。
  • 需要跨调用保留的租户数据必须存放在 Host 服务中。Runner 使用 ctx.state_get() / ctx.state_set() 等接口(需要 ctx.context.available_apis.state 为 true)。进程内缓存只允许存放制品级公共数据;如果确实要缓存租户数据,键必须包含完整安装标识,并能在安装被移除时清理。
  • 不要发起携带租户数据的后台任务或线程:SDK 不跟踪你自己创建的任务的所有权。如果确实要脱离处理函数运行,请以安装绑定为键维护一份登记表,并在 on_installation_revoked() 中停止它们。处理函数返回后,本次调用的上下文和运行关联调用即失效。

一个安装,多份用法

一个 workspace 内每个插件只安装一次,但同一份安装会被多个产品对象复用:

组件类型 复用它的是 隔离靠什么
Agent / 流水线中的 Runner 每个处理器 ctx.state.* 已按处理器隔离(作用域含 binding_identity,由 Agent UUID 派生)
知识库用的 KnowledgeEngine / Parser 每个知识库 需要插件自己按知识库 ID 隔离
Tool / Command / EventListener / Page 多个处理器、会话或页面请求 依赖调用时读取,不能在实例上累积

平台的插件存储键是 (实例, 工作区, 组件类型, 插件名, key),不包含处理器或知识库维度。所以只要你的插件要持久化或缓存,就必须自己把产品对象的身份拼进键里:

  • 配置:每次调用读取(ctx.config,或 task-local 的 self.get_plugin_config()),不要缓存到实例。
  • 持久化:键必须包含产品对象身份(处理器身份 / 知识库 ID / 会话身份)。
  • 进程内缓存:键必须同时包含完整安装绑定和产品对象身份,并在安装被撤销时释放(见下)。
  • 锁与栅栏:必须按 (安装, 产品对象) 记。一个处理器或知识库的失败,不得阻断同一安装下的其他对象。
  • 失败分级:只有"已经派发、结果未知"的变更才应进入需要人工对账的状态;确定性的校验错误和调用前的取消不应影响后续调用。
  • 日志与遥测:诊断数据的失败不得影响业务,也不要把租户数据写进进程级单例。

安装被撤销时

共享模式下,一个进程内只有一个对象图,服务同一制品的所有安装。所以某个安装被撤销时对象图不会销毁,插件必须自己释放按安装绑定记的进程内状态:

class MyPlugin(BasePlugin):
    def __init__(self) -> None:
        super().__init__()
        self._caches: dict[str, Cache] = {}

    async def on_installation_revoked(self, binding) -> None:
        self._caches.pop(binding.installation_uuid, None)
  • 触发时机:卸载、停用、移除工作区,以及升级(旧绑定被撤销)。只有共享模式会调用;专属模式下进程会被关停,进程内状态随进程消失。
  • 调用时没有调用上下文,Host API(存储、配置、绑定查询)不可用——只能释放进程内状态,安装身份由参数给出。
  • 抛出异常会被记录,不影响撤销流程。
  • 需要带 BasePlugin.on_installation_revoked 的 SDK 版本;旧版本上这个钩子只是永远不会被调用。

允许 / 禁止对照

场景 允许 禁止
读取配置 每次调用时读取(self.get_plugin_config()、ctx.config) 在 initialize() 中读取后保存到 self
连接池 / HTTP 客户端 不含租户凭据的共享连接池 把安装的 endpoint、api key 固化到实例上
缓存 制品级公共数据;或键为完整安装标识且支持清理 模块级 dict 以工作区或用户为键长期驻留
跨调用状态 Host 服务(Runner 用 ctx.state_*) 实例字段、模块级全局变量
后台工作 无 处理函数返回后继续持有本次上下文或其关联调用
日志 ctx.log() 或 SDK 日志 API 模块级后台线程直接输出租户配置或凭据

常见反例

1. 在初始化时读取安装配置

async def initialize(self):
    self.endpoint = self.plugin.config["endpoint"]  # 进程级初始化,没有当前安装
    self.api_key = self.plugin.config["api_key"]

共享初始化阶段没有安装配置,这段代码只会读到空值或上一次的值。

2. 把租户数据放进模块级缓存

_CACHE = {}  # 以工作区或用户为键,会跨安装、跨租户长期驻留

async def call(self, params, **kwargs):
    if params["user"] not in _CACHE:
        _CACHE[params["user"]] = await fetch(params["user"])

3. 处理函数返回后继续使用本次调用

async def call(self, params, **kwargs):
    config = self.get_plugin_config()
    asyncio.create_task(self._upload_later(config))  # 处理函数已返回,上下文与授权均已失效
    return ok()

4. 把实例字段当作配置容器

async def initialize(self):
    self.default_endpoint = "https://api.example.com"  # 公共常量没有问题
    self.last_config = {}  # 一旦写入安装配置就违规

正确写法:

class LookupTool(Tool):
    async def initialize(self):
        # 只放置制品级、不含租户信息的对象
        self.transport = StatelessHTTPTransport()

    async def call(self, params, **kwargs):
        config = self.get_plugin_config()  # 每次调用按当前安装读取
        return await self.transport.lookup(config["endpoint"], params)

Runner 组件特别注意

Runner 与无状态约束的张力最大,因为运行器天然倾向于在进程内保存会话与上下文。

  • RunnerContext 只在本次运行期间有效,不要把它或它的字段保存到实例上跨调用复用。
  • Runner 需要跨调用保留的会话状态,走 ctx.state_get() / ctx.state_set()(作用域支持 conversation、actor、subject、runner)或 Host 提供的其他存储,不要使用实例字段。
  • 处理函数返回后,不要继续在后台任务中使用 ctx,也不要继续使用通过 self.plugin 发起的运行关联调用。
  • 如果 Runner 的设计必须依赖进程内持有的跨调用状态(例如需要长期驻留的外部会话进程),不要声明 stateless-v1,继续以专属 Worker 运行即可。

运行上下文的完整接口见运行上下文(RunnerContext)。

自检与提交

提交审核前逐条核对:

  • manifest.yaml 中两个字段都已声明,取值正确。
  • 全文搜索,确认没有模块级可变全局变量。
  • 确认 initialize() 只做进程级初始化,不读取安装配置。
  • 确认所有安装配置都在调用时读取。
  • 确认没有在处理函数返回后继续运行的任务或线程。
  • 确认跨调用状态通过 Host API 保存(Runner 使用 ctx.state_*)。

提交方式

认证与发布走同一条链路,不需要额外流程:在 manifest.yaml 中声明上面两个字段,然后照常用 lbp publish 提交一个新版本(已发布的版本无法追加声明)。LangBot Space 会检查本次提交的精确插件包,进行辅助代码审查,并在人工批准后签发认证插件标识;认证绑定到已审核的版本与校验和。

如果审核发现该版本无法满足要求,请先把它作为未签名的普通插件发布,并在后续不可变版本中修复问题。

如果核对下来发现插件并不适合共享运行,直接以普通插件提交即可,不需要认证。发布流程见发布到插件市场。

LangBot Documentation

Home

简体中文
指南
开发者
文章
API 参考
Other pages
English
Guides
Developers
Articles
API Reference
Other pages
日本語
ガイド
開発者
記事
API リファレンス
Other pages

Clone this wiki locally