mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-29 03:46:37 +08:00
docs(plugin): refocus the certification page on developer decisions
Rewrite plugin/certified-plugins in all three locales around the questions a plugin developer actually asks, and make it the single home for certification: what it is, why it exists, when it is needed, what changes after approval, and what the contract requires. - Decision framing restored, including a needs/does-not-need table and the explicit statement that most plugins do not need certification. - Full manifest example, accepted values for both declaration fields and the SDK version that recognises them. - Constraints kept and tightened: stateless rules, allowed/forbidden table, anti-patterns with correct/incorrect snippets, Runner notes, self-check list. - Submission flow moved onto this page so it no longer depends on the publishing page. - Operator content that duplicates the Core architecture document removed (Cloud admission matrix, OSS issuer keys, certificate fields, internal review tiers). publish/market is now publishing-only with a one-line pointer, and its misplaced plugin_submission screenshot moves back under the upload section where it belongs. dev/basic-info replaces the commented-out manifest fields with a link to the contract page. ja had been missing whole sections (36 lines); all three locales now share the same section structure.
This commit is contained in:
+158
-27
@@ -1,58 +1,189 @@
|
||||
---
|
||||
title: "Certified plugins and stateless components"
|
||||
description: "Why certified plugins share workers and how to implement stateless-v1 components."
|
||||
title: "Certified plugins and shared runtime"
|
||||
description: "What certification is, why it exists, when you need it, and the shared-runtime-v1 / stateless-v1 contract."
|
||||
---
|
||||
|
||||
Certification means that an exact immutable plugin artifact is approved for cross-tenant sharing in LangBot Cloud; it is not a general quality or safety badge. An installed artifact with a valid trusted certificate must run in shared mode, not dedicated mode. A marketplace **Certified Plugin** badge alone does not prove that the downloaded ZIP passes verification or that a live Worker is shared. Installations of the same artifact digest share one Worker, one `BasePlugin` object and one instance of each component. Each installation contributes only lightweight configuration, authority and lifecycle context. “Each component” means one instance for every declared component manifest across EventListener, Tool, Command, KnowledgeEngine, Parser, Page and Runner.
|
||||
## What certification is
|
||||
|
||||
## Declaration
|
||||
Certification is a grant LangBot makes for **one exact, immutable plugin artifact**: that artifact is approved to run **shared across tenants** in LangBot Cloud.
|
||||
|
||||
- Certification is bound to the artifact itself (a specific published version, identified by digest), not to a plugin name or an author. Publishing a new version does not carry the previous version's certification over.
|
||||
- Certification is not a quality score and not a security audit endorsement.
|
||||
|
||||
## Why certification exists
|
||||
|
||||
LangBot does not place plugins in shared mode by default. The reason is that sharing is keyed by artifact:
|
||||
|
||||
- **Dedicated mode**: the plugin does not participate in sharing and runs on its own.
|
||||
- **Shared mode**: every installation of the same artifact digest shares one Worker.
|
||||
|
||||
Sharing keeps the number of processes and the startup cost from growing linearly with the number of installations. The trade-off is that **one process handles calls from several Workspaces at once** — so a plugin that keeps tenant data on its instances will leak it between tenants.
|
||||
|
||||
The platform therefore has to review this exact artifact, confirm it satisfies the stateless contract, and then pin that trust to the artifact with a signature before it may enter shared placement. That is what certification is for.
|
||||
|
||||
## When you need certification
|
||||
|
||||
**Most plugins do not need certification.** Skipping it does not stop you from publishing to the marketplace, and it does not stop users from installing your plugin — it simply runs in dedicated mode.
|
||||
|
||||
| Your situation | Certification needed? |
|
||||
| --- | --- |
|
||||
| You only want to publish the plugin so users can install it | No |
|
||||
| The plugin needs process-local state across calls (for example a long-lived external session process) | No — and it should stay dedicated |
|
||||
| The plugin has background tasks, timers, or long-lived tenant caches | No — dedicated mode is the safer choice |
|
||||
| You want the Certified Plugin badge | Yes |
|
||||
| You want several installations of one artifact to share a process, reducing cost and startup overhead | Yes |
|
||||
| The plugin targets Cloud multi-tenant scenarios and you want shared placement | Yes |
|
||||
|
||||
The test is a single question: **certification only makes sense if your code genuinely keeps no tenant or installation data on its instances.**
|
||||
|
||||
## What certification changes
|
||||
|
||||
After review passes:
|
||||
|
||||
- The artifact is signed and installed in **shared mode**; multiple installations of the same digest share one Worker.
|
||||
- That Worker holds one `BasePlugin` object and one instance per declared component manifest (EventListener, Tool, Command, KnowledgeEngine, Parser, Page, Runner).
|
||||
- Each installation adds only a lightweight layer of configuration, authority and lifecycle context; the active one is selected per call.
|
||||
- You receive the Certified Plugin badge.
|
||||
- Self-hosted OSS ships without a certification issuer public key, so a certified artifact still runs dedicated on self-hosted instances.
|
||||
|
||||
You also take on a standing obligation: **every later version must keep satisfying the stateless contract.** After changing code you submit a new version for review; certification does not renew itself.
|
||||
|
||||
## What you must adapt
|
||||
|
||||
### Declaration
|
||||
|
||||
Declare both fields under `execution` in `manifest.yaml`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Plugin
|
||||
metadata:
|
||||
author: your-name
|
||||
name: YourPlugin
|
||||
version: 0.1.1
|
||||
spec:
|
||||
config: []
|
||||
components: {}
|
||||
execution:
|
||||
python:
|
||||
path: main.py
|
||||
attr: Plugin
|
||||
attr: YourPlugin
|
||||
sharedRuntime: shared-runtime-v1
|
||||
componentModel: stateless-v1
|
||||
```
|
||||
|
||||
Both fields are required. The declaration is an application, not approval: authenticated artifact-bound advisory source review, explicit human approval and a valid trusted signature over the exact artifact remain mandatory. Advisory review is an internal prerequisite, not an intermediate certification tier.
|
||||
| Field | Accepted value | Notes |
|
||||
| --- | --- | --- |
|
||||
| `execution.sharedRuntime` | `shared-runtime-v1` | This is the only accepted value; anything else fails manifest validation |
|
||||
| `execution.componentModel` | `stateless-v1` | This is the only accepted value |
|
||||
|
||||
## Stateless component contract
|
||||
- Both fields **must be declared together**. A package that declares `sharedRuntime` without `componentModel` is rejected during certification validation.
|
||||
- The fields are recognised from **SDK 0.7.0** onwards. Earlier SDKs ignore them, so do not rely on an old SDK to tell you whether the declaration took effect.
|
||||
|
||||
- Plugin and component `initialize()` methods are process-scoped and run once per Worker.
|
||||
- Component objects must be re-entrant because calls from multiple Workspaces can overlap.
|
||||
- On `BasePlugin`, read installation configuration with `self.get_config()`. In a component, use `self.get_plugin_config()` or `self.plugin.get_config()`.
|
||||
- Never retain configuration, credentials, Workspace/installation identity or request data in instance fields or module globals.
|
||||
- Do not capture tenant configuration in `initialize()`.
|
||||
- Use SDK Host APIs for storage, files, credentials, logs, models and other platform capabilities. Authority comes from the task-local invocation context.
|
||||
- Durable tenant state belongs in Host services. Process caches may contain artifact-level public data only, or must use the complete installation binding as their key and support revocation cleanup.
|
||||
- Tenant-bearing detached tasks and threads are forbidden in `stateless-v1`; the SDK does not yet expose a plugin-facing ownership/revocation API.
|
||||
<Warning>
|
||||
A declaration is an application, not approval. It grants no badge and does not change how the plugin runs by itself.
|
||||
</Warning>
|
||||
|
||||
The contract intentionally preserves serverless-ready semantics, but current releases still run long-lived Workers and do not offer a serverless deployment mode.
|
||||
### Stateless constraints
|
||||
|
||||
## Configuration example
|
||||
- `BasePlugin.initialize()` and component `initialize()` are **process-scoped** and run once per Worker. At that point there is no "current installation" and no "current Workspace".
|
||||
- Component instances are called concurrently by different Workspaces, so every method must be re-entrant.
|
||||
- Read the active installation configuration per call: `self.get_config()` on `BasePlugin`, `self.get_plugin_config()` or `self.plugin.get_config()` in a component, and `ctx.config` in a Runner.
|
||||
- Never store configuration, credentials, Workspace or installation identity, or request data in instance fields or module-level globals.
|
||||
- Do not read and cache installation configuration in `initialize()`.
|
||||
- Reach storage, files, credentials, logs, models and other platform capabilities through the SDK Host APIs. Authority is resolved per call; do not cache it yourself.
|
||||
- Durable tenant state belongs in Host services. Runners use `ctx.state_get()` / `ctx.state_set()` and friends (this requires `ctx.context.available_apis.state` to be `true`). In-process caches may hold artifact-level public data only; if you must cache tenant data, the key has to include the complete installation identity and must be cleaned up when the installation is removed.
|
||||
- Tenant-bearing background tasks and threads are forbidden. Once a handler returns, that invocation's context and run-associated calls are no longer valid.
|
||||
|
||||
### Allowed / forbidden
|
||||
|
||||
| Concern | Allowed | Forbidden |
|
||||
| --- | --- | --- |
|
||||
| Reading config | Read per call (`self.get_plugin_config()`, `ctx.config`) | Reading in `initialize()` and keeping it on `self` |
|
||||
| Connection pools / HTTP clients | Shared pools without tenant credentials | Baking an installation's endpoint or API key into the instance |
|
||||
| Caching | Artifact-level public data; or keys including the full installation identity and supporting cleanup | A module-level `dict` keyed by Workspace or user that persists |
|
||||
| Cross-invocation state | Host services (`ctx.state_*` in a Runner) | Instance fields, module globals |
|
||||
| Background work | None | Holding an invocation's context or its run-associated calls after the handler returned |
|
||||
| Logging | `ctx.log()` or SDK logging APIs | A module-level background thread printing tenant config or credentials |
|
||||
|
||||
### Common anti-patterns
|
||||
|
||||
**1. Reading installation config during initialization**
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.endpoint = self.plugin.config["endpoint"] # process-scoped init has no active installation
|
||||
self.api_key = self.plugin.config["api_key"]
|
||||
```
|
||||
|
||||
Shared initialization has no installation configuration, so this reads empty or stale values.
|
||||
|
||||
**2. Caching tenant data in a module global**
|
||||
|
||||
```python
|
||||
_CACHE = {} # keyed by Workspace or user; persists across installations and tenants
|
||||
|
||||
async def call(self, params, **kwargs):
|
||||
if params["user"] not in _CACHE:
|
||||
_CACHE[params["user"]] = await fetch(params["user"])
|
||||
```
|
||||
|
||||
**3. Keeping work alive after the handler returns**
|
||||
|
||||
```python
|
||||
async def call(self, params, **kwargs):
|
||||
config = self.get_plugin_config()
|
||||
asyncio.create_task(self._upload_later(config)) # handler already returned; context and authority are gone
|
||||
return ok()
|
||||
```
|
||||
|
||||
**4. Treating instance fields as a config container**
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.default_endpoint = "https://api.example.com" # a public constant is fine
|
||||
self.last_config = {} # writing installation config here is not
|
||||
```
|
||||
|
||||
The correct shape:
|
||||
|
||||
```python
|
||||
class LookupTool(Tool):
|
||||
async def initialize(self):
|
||||
# artifact-level objects only, no tenant information
|
||||
self.transport = StatelessHTTPTransport()
|
||||
|
||||
async def call(self, params, **kwargs):
|
||||
config = self.get_plugin_config()
|
||||
config = self.get_plugin_config() # read for the active installation on every call
|
||||
return await self.transport.lookup(config["endpoint"], params)
|
||||
```
|
||||
|
||||
Do not retain tenant data during process initialization:
|
||||
### Runners in particular
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.endpoint = self.plugin.config["endpoint"]
|
||||
self.api_key = self.plugin.config["api_key"]
|
||||
```
|
||||
Runners have the most tension with the stateless contract, because a runner naturally tends to keep sessions and context in the process.
|
||||
|
||||
Shared initialization has no tenant config, so this reads empty/stale data or fails. Any attempt to retain invocation config on the shared object blocks certification.
|
||||
- A `RunnerContext` is **valid only for the current run**. Do not keep it, or anything derived from it, on the instance for reuse across calls.
|
||||
- Session state a Runner needs across calls belongs in `ctx.state_get()` / `ctx.state_set()` (scopes: `conversation`, `actor`, `subject`, `runner`) or another Host-provided store — not in instance fields.
|
||||
- After a handler returns, do not keep using `ctx` in background tasks, and do not keep using run-associated calls made through `self.plugin`.
|
||||
- If your Runner fundamentally depends on process-local state across calls (for example a long-lived external session process), **do not declare `stateless-v1`** and stay on a dedicated Worker.
|
||||
|
||||
## Legacy compatibility
|
||||
See [Runner context](/en/plugin/dev/apis/agent-run) for the full context API.
|
||||
|
||||
Legacy source and unsigned packages remain compatible on dedicated Workers, including unsigned Cloud installs. There is no dedicated certification or dedicated signature. Cloud must reject malformed, forged, untrusted or legacy signed certificates lacking the `stateless-v1` claim; they are not unsigned fallbacks. OSS ships without a built-in certification issuer public key, supports one Workspace and does not support configuring a key to enable shared-certified execution. Existing installations and a marketplace badge are not proof of shared Worker placement. Changing only the manifest does not satisfy certification review.
|
||||
## Self-check and submission
|
||||
|
||||
Verify each item before submitting:
|
||||
|
||||
- [ ] Both fields are declared in `manifest.yaml` with the exact accepted values.
|
||||
- [ ] A repository-wide search confirms there are no module-level mutable globals.
|
||||
- [ ] `initialize()` performs process-scoped setup only and reads no installation config.
|
||||
- [ ] Every piece of installation config is read at call time.
|
||||
- [ ] No task or thread keeps running after a handler returns.
|
||||
- [ ] Cross-call state goes through Host APIs (`ctx.state_*` in a Runner).
|
||||
|
||||
### How to submit
|
||||
|
||||
Certification uses the same path as publishing, with no extra process: declare the two fields above in `manifest.yaml`, then submit a **new version** with `lbp publish` as usual (a published version cannot have the declaration added later). LangBot Space checks the exact submitted package, runs an advisory code review, and issues the Certified Plugin badge after human approval; certification is bound to the reviewed version and checksum.
|
||||
|
||||
If the review finds that the release cannot meet the requirements, publish it as an unsigned normal plugin first and fix the findings in a later immutable version.
|
||||
|
||||
If this check shows your plugin is not a good fit for sharing, just publish it as a normal plugin — certification is optional. See [Publish to the marketplace](/en/plugin/dev/publish/market) for the publishing flow.
|
||||
|
||||
@@ -28,11 +28,10 @@ execution:
|
||||
python:
|
||||
path: main.py # Do not modify
|
||||
attr: HelloPlugin # Do not modify
|
||||
# Add only after migrating to the stateless singleton certification contract:
|
||||
# sharedRuntime: shared-runtime-v1
|
||||
# componentModel: stateless-v1
|
||||
```
|
||||
|
||||
To certify your plugin and let several installations share one runtime, also declare `sharedRuntime` and `componentModel` under `execution`; see [Certified plugins and shared runtime](/en/plugin/certified-plugins).
|
||||
|
||||
Plugin multilingual support follows the [RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646) standard. Currently supported languages include:
|
||||
|
||||
- `en_US` English (required)
|
||||
|
||||
@@ -78,14 +78,8 @@ Plugin built successfully: dist/tmp/RockChinQ-HelloPlugin-0.1.0.lbpkg
|
||||
|
||||
You can now check your plugin submission task at [LangBot Plugin Marketplace](https://space.langbot.app/market). Please wait for approval, after which you can view your plugin in the marketplace.
|
||||
|
||||
## Request Certified Plugin review
|
||||
|
||||
If your release is designed for certification, declare both `execution.sharedRuntime: shared-runtime-v1` and `execution.componentModel: stateless-v1` in `manifest.yaml`, then submit a **new version**. The advisory review verifies singleton components are stateless, re-entrant, read config per invocation and retain no tenant data. The declaration alone grants no badge or runtime privilege.
|
||||
|
||||
LangBot Space checks the exact submitted package, runs an advisory code review, and requires a human approval before a Certified Plugin badge can be issued. Certification is bound to the reviewed package version and checksum. Keep workspace configuration, storage, credentials, logs, and background work scoped to the active installation. Do not rely on mutable process-global state.
|
||||
|
||||
If the review finds that the release cannot meet these requirements, submit an unsigned normal dedicated plugin and address the findings in a later immutable version. There is no dedicated certification or intermediate certification tier. See [Certified Plugins](/en/plugin/certified-plugins) for user-visible behavior.
|
||||
|
||||
<img width="600" src="/images/zh/plugin/dev/plugin_submission.png" />
|
||||
|
||||
Publishing does not involve certification: plugins run in dedicated mode by default. If this release should also be certified, see [Certified plugins and shared runtime](/en/plugin/certified-plugins).
|
||||
|
||||
If you have any questions, you can send an email to `[email protected]` or contact us through [Community](/en/insight/community).
|
||||
|
||||
+171
-18
@@ -1,36 +1,189 @@
|
||||
---
|
||||
title: "認証プラグインとステートレスコンポーネント"
|
||||
description: "認証プラグインの共有 Worker と stateless-v1 実装規約。"
|
||||
title: "認証プラグインと共有実行"
|
||||
description: "認証とは何か、なぜ存在するのか、いつ必要か、そして shared-runtime-v1 / stateless-v1 の規約。"
|
||||
---
|
||||
|
||||
認証は、正確で不変なプラグイン artifact が LangBot Cloud でテナント間共有できることを意味し、一般的な品質・安全評価ではありません。インストールされた artifact に有効な信頼済み署名がある場合は shared モードで実行し、dedicated にフォールバックしてはいけません。マーケットプレイスの**認証プラグイン**表示だけでは、ZIP の検証や実際の Worker 共有は証明できません。同じ artifact digest の installation は、1 つの Worker、1 つの `BasePlugin`、各種コンポーネントの単一インスタンスを共有します。installation ごとに保持するのは、軽量な設定・権限・ライフサイクルコンテキストだけです。「各コンポーネント」は EventListener、Tool、Command、KnowledgeEngine、Parser、Page、Runner の宣言済み manifest ごとに 1 インスタンスという意味です。
|
||||
## 認証とは
|
||||
|
||||
## 宣言
|
||||
認証とは、LangBot が**正確で不変な 1 つのプラグイン artifact** に対して付与する資格です。その artifact は LangBot Cloud で**テナントをまたいで共有実行**することが承認されます。
|
||||
|
||||
- 認証は artifact そのもの(digest で識別される特定の公開バージョン)に紐付き、プラグイン名や作者には紐付きません。新しいバージョンを公開しても、前のバージョンの認証は引き継がれません。
|
||||
- 認証は品質評価ではなく、セキュリティ監査の保証でもありません。
|
||||
|
||||
## なぜ認証が必要か
|
||||
|
||||
LangBot は既定ではプラグインを共有実行しません。共有の単位が artifact だからです。
|
||||
|
||||
- **専用モード**:共有に参加せず、単独で実行します。
|
||||
- **共有モード**:同じ artifact digest のすべての installation が 1 つの Worker を共有します。
|
||||
|
||||
共有の利点は、プロセス数と起動コストが installation 数に比例して増えないことです。代償は、**1 つのプロセスが複数の Workspace からの呼び出しを同時に処理する**ことです。インスタンスにテナントデータを置いているプラグインは、テナント間でデータが混ざります。
|
||||
|
||||
そのためプラットフォームは、この artifact を審査してステートレス規約を満たすことを確認し、その信頼を署名で artifact に固定したうえで、共有配置に載せます。これが認証の目的です。
|
||||
|
||||
## 認証が必要な場合
|
||||
|
||||
**ほとんどのプラグインには認証は不要です。** 認証しなくてもマーケットプレイスへの公開は可能で、ユーザーの利用も妨げられません。プラグインは専用モードで動作します。
|
||||
|
||||
| 状況 | 認証の要否 |
|
||||
| --- | --- |
|
||||
| マーケットプレイスに公開してユーザーに使ってもらいたいだけ | 不要 |
|
||||
| 呼び出しをまたぐプロセス内状態が必要(常駐する外部セッションプロセスなど) | 不要。専用実行のままにしてください |
|
||||
| バックグラウンドタスク、タイマー、長期のテナントキャッシュがある | 不要。専用モードのほうが安全です |
|
||||
| 認証プラグインの表示がほしい | 必要 |
|
||||
| 同じ artifact の複数 installation で 1 つのプロセスを共有し、コストと起動負荷を下げたい | 必要 |
|
||||
| Cloud のマルチテナント向けで、共有配置されたい | 必要 |
|
||||
|
||||
判断基準は 1 つだけです。**インスタンスにテナントや installation に紐づくデータを一切保存しないと本当に言い切れる場合にのみ、認証に意味があります。**
|
||||
|
||||
## 認証後の効果
|
||||
|
||||
審査を通過すると、次のようになります。
|
||||
|
||||
- artifact が署名され、インストール時は**共有モード**で実行されます。同じ digest の複数の installation が 1 つの Worker を共有します。
|
||||
- その Worker 内には `BasePlugin` が 1 つ、宣言した各コンポーネント manifest につき 1 つのインスタンス(EventListener、Tool、Command、KnowledgeEngine、Parser、Page、Runner)だけが存在します。
|
||||
- 各 installation は軽量な設定・権限・ライフサイクルコンテキストを 1 つだけ保持し、呼び出しごとに現在のリクエストがどれを使うかを決定します。
|
||||
- 認証プラグインの表示が付与されます。
|
||||
- セルフホスト OSS は認証発行者の公開鍵を既定で持たないため、認証済みの artifact でもセルフホスト環境では専用モードで動作します。
|
||||
|
||||
同時に継続的な義務も発生します。**以降のすべてのバージョンがステートレス規約を満たし続ける必要があります。** コードを変更したら新しいバージョンを提出して再審査を受けます。認証は自動更新されません。
|
||||
|
||||
## 必要な対応
|
||||
|
||||
### 宣言
|
||||
|
||||
`manifest.yaml` の `execution` 配下に 2 つのフィールドを同時に宣言します。
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Plugin
|
||||
metadata:
|
||||
author: your-name
|
||||
name: YourPlugin
|
||||
version: 0.1.1
|
||||
spec:
|
||||
config: []
|
||||
components: {}
|
||||
execution:
|
||||
python:
|
||||
path: main.py
|
||||
attr: Plugin
|
||||
attr: YourPlugin
|
||||
sharedRuntime: shared-runtime-v1
|
||||
componentModel: stateless-v1
|
||||
```
|
||||
|
||||
2 つのフィールドは必須です。宣言だけでは認証されません。正確な artifact に紐付いた信頼できる補助レビュー、人による明示的な承認、有効な署名が必要です。補助レビューは内部の前提条件であり、中間の認証段階ではありません。
|
||||
| フィールド | 有効な値 | 説明 |
|
||||
| --- | --- | --- |
|
||||
| `execution.sharedRuntime` | `shared-runtime-v1` | この値のみ受け付けます。他の値を書くとマニフェスト検証に失敗します |
|
||||
| `execution.componentModel` | `stateless-v1` | この値のみ受け付けます |
|
||||
|
||||
## ステートレス規約
|
||||
- 2 つのフィールドは**必ず同時に**宣言します。`sharedRuntime` だけを宣言し `componentModel` を書かないパッケージは、認証検証の段階で拒否されます。
|
||||
- これらのフィールドは **SDK 0.7.0** 以降で認識されます。それ以前の SDK は無視するため、古い SDK で宣言の有効性を確認しないでください。
|
||||
|
||||
- プラグインとコンポーネントの `initialize()` は Worker ごとに一度だけ実行します。
|
||||
- 異なる Workspace の呼び出しが同じオブジェクト上で並行するため、コンポーネントは再入可能にします。
|
||||
- `BasePlugin` では `self.get_config()`、コンポーネントでは `self.get_plugin_config()` または `self.plugin.get_config()` で現在の installation 設定を取得します。
|
||||
- 設定、認証情報、Workspace/installation ID、リクエストデータをインスタンス属性やモジュールグローバルに保存しません。
|
||||
- `initialize()` でテナント設定を読み取り、保持してはいけません。
|
||||
- ストレージ、ファイル、認証情報、ログ、モデルなどは SDK の Host API を使います。権限は task-local invocation context から解決されます。
|
||||
- 永続的なテナント状態は Host サービスに置きます。プロセス内キャッシュは artifact 共通データだけにするか、完全な installation binding をキーにし、失効時に解放できるようにします。
|
||||
- `stateless-v1` ではテナントデータを持つ detached task/thread を禁止します。SDK はまだプラグイン向けの所有権・失効 API を提供していません。
|
||||
<Warning>
|
||||
宣言は申請であり、承認ではありません。宣言だけでは認証表示も付与されず、実行モードも変わりません。
|
||||
</Warning>
|
||||
|
||||
この規約は将来の serverless 実行に適した意味論を保ちますが、現在は長寿命 Worker を使用しており、serverless 配備モードは提供していません。
|
||||
### ステートレス規約
|
||||
|
||||
## 旧プラグインとの互換性
|
||||
- `BasePlugin.initialize()` とコンポーネントの `initialize()` は**プロセス単位**の初期化で、Worker ごとに 1 回だけ実行されます。この時点では「現在のインストール」も「現在の Workspace」も存在しません。
|
||||
- コンポーネントインスタンスは異なる Workspace から同時に呼び出されるため、すべてのメソッドは再入可能でなければなりません。
|
||||
- 現在のインストール設定は呼び出しごとに読み取ります。`BasePlugin` では `self.get_config()`、コンポーネントでは `self.get_plugin_config()` または `self.plugin.get_config()`、Runner では `ctx.config` を使用します。
|
||||
- 設定、資格情報、Workspace / installation ID、リクエストデータをインスタンス属性やモジュールグローバルに保存しないでください。
|
||||
- `initialize()` でインストール設定を読み取り、保持しないでください。
|
||||
- ストレージ、ファイル、資格情報、ログ、モデルなどのプラットフォーム機能は SDK の Host API 経由で利用します。権限は呼び出しごとに解決されるため、自分でキャッシュしないでください。
|
||||
- 呼び出しをまたいで保持する必要があるテナント状態は Host サービスに置きます。Runner は `ctx.state_get()` / `ctx.state_set()` などを使用します(`ctx.context.available_apis.state` が `true` である必要があります)。プロセス内キャッシュは artifact レベルの公開データのみ許可されます。テナントデータをキャッシュする場合は、キーに完全な installation 識別子を含め、インストール削除時に解放できるようにしてください。
|
||||
- テナントデータを伴うバックグラウンドタスクやスレッドは禁止です。ハンドラーが戻った時点で、その呼び出しのコンテキストと実行関連の呼び出しは無効になります。
|
||||
|
||||
既存のソースと未署名パッケージは専用 Worker で互換性を維持し、Cloud でも未署名の専用インストールが可能です。専用実行の認証や署名はありません。Cloud は不正・偽造・未信頼、または `stateless-v1` の署名済み宣言を欠く旧形式の証明書を、未署名扱いせず拒否します。セルフホスト OSS は認証発行者の公開鍵を内蔵せず、単一 Workspace のみサポートします。OSS で鍵を設定して認証共有実行を行うことはサポートされません。表示や既存のインストールは共有の証明ではなく、manifest の変更だけでは認証要件を満たしません。
|
||||
### 許可 / 禁止の対照表
|
||||
|
||||
| 項目 | 許可 | 禁止 |
|
||||
| --- | --- | --- |
|
||||
| 設定の読み取り | 呼び出しごとに読み取る(`self.get_plugin_config()`、`ctx.config`) | `initialize()` で読み取り `self` に保持する |
|
||||
| コネクションプール / HTTP クライアント | テナント資格情報を含まない共有プール | インストールの endpoint や api key をインスタンスに固定する |
|
||||
| キャッシュ | artifact レベルの公開データ、または完全な installation 識別子をキーとし解放可能なもの | Workspace やユーザーをキーにしたモジュールレベルの `dict` を長期間保持する |
|
||||
| 呼び出しをまたぐ状態 | Host サービス(Runner は `ctx.state_*`) | インスタンス属性、モジュールグローバル |
|
||||
| バックグラウンド処理 | なし | ハンドラーが戻った後もそのコンテキストや実行関連の呼び出しを保持する |
|
||||
| ログ | `ctx.log()` または SDK のログ API | モジュールレベルのバックグラウンドスレッドからテナント設定や資格情報を出力する |
|
||||
|
||||
### よくある反例
|
||||
|
||||
**1. 初期化時にインストール設定を読み取る**
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.endpoint = self.plugin.config["endpoint"] # プロセス単位の初期化。現在のインストールは存在しない
|
||||
self.api_key = self.plugin.config["api_key"]
|
||||
```
|
||||
|
||||
共有初期化の段階にはインストール設定がないため、空の値または前回の値しか読めません。
|
||||
|
||||
**2. テナントデータをモジュールグローバルにキャッシュする**
|
||||
|
||||
```python
|
||||
_CACHE = {} # Workspace やユーザーをキーにすると、インストールやテナントをまたいで残留する
|
||||
|
||||
async def call(self, params, **kwargs):
|
||||
if params["user"] not in _CACHE:
|
||||
_CACHE[params["user"]] = await fetch(params["user"])
|
||||
```
|
||||
|
||||
**3. ハンドラーが戻った後も処理を続ける**
|
||||
|
||||
```python
|
||||
async def call(self, params, **kwargs):
|
||||
config = self.get_plugin_config()
|
||||
asyncio.create_task(self._upload_later(config)) # ハンドラーは既に戻っており、コンテキストと権限は無効
|
||||
return ok()
|
||||
```
|
||||
|
||||
**4. インスタンス属性を設定の入れ物として扱う**
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.default_endpoint = "https://api.example.com" # 公開定数は問題ない
|
||||
self.last_config = {} # インストール設定を書き込むと違反になる
|
||||
```
|
||||
|
||||
正しい形:
|
||||
|
||||
```python
|
||||
class LookupTool(Tool):
|
||||
async def initialize(self):
|
||||
# artifact レベルでテナント情報を含まないオブジェクトのみを置く
|
||||
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` 経由の実行関連呼び出しを使い続けたりしないでください。
|
||||
- 設計上どうしてもプロセス内の呼び出しをまたぐ状態(長期間常駐する外部セッションプロセスなど)に依存する場合、**`stateless-v1` を宣言しないでください**。専用 Worker のままにします。
|
||||
|
||||
コンテキスト API の全体は[実行コンテキスト(RunnerContext)](/ja/plugin/dev/apis/agent-run)を参照してください。
|
||||
|
||||
## 自己チェックと提出
|
||||
|
||||
提出前に 1 つずつ確認してください。
|
||||
|
||||
- [ ] `manifest.yaml` に 2 つのフィールドが正しい値で宣言されている。
|
||||
- [ ] リポジトリ全体を検索し、モジュールレベルの可変グローバルが存在しないことを確認した。
|
||||
- [ ] `initialize()` はプロセス単位の初期化のみを行い、インストール設定を読み取っていない。
|
||||
- [ ] すべてのインストール設定を呼び出し時に読み取っている。
|
||||
- [ ] ハンドラーが戻った後に動作し続けるタスクやスレッドがない。
|
||||
- [ ] 呼び出しをまたぐ状態は Host API 経由で保存している(Runner は `ctx.state_*`)。
|
||||
|
||||
### 提出方法
|
||||
|
||||
認証は公開と同じ経路で、追加の手続きはありません。`manifest.yaml` に上記 2 つのフィールドを宣言し、いつもどおり `lbp publish` で**新しいバージョン**を提出します(公開済みのバージョンに後から宣言を追加することはできません)。LangBot Space は提出された正確なパッケージを確認し、補助的なコードレビューを行い、人による承認後に認証プラグインの表示を発行します。認証は審査済みのバージョンとチェックサムに紐付きます。
|
||||
|
||||
要件を満たせないと判断された場合は、まず署名なしの通常のプラグインとして公開し、後続の不変バージョンで修正してください。
|
||||
|
||||
このチェックで共有実行に不向きだと分かった場合は、通常のプラグインとして提出してください。認証は必須ではありません。公開の流れは[マーケットプレイスへの公開](/ja/plugin/dev/publish/market)を参照してください。
|
||||
|
||||
@@ -28,11 +28,10 @@ execution:
|
||||
python:
|
||||
path: main.py # 変更しないでください
|
||||
attr: HelloPlugin # 変更しないでください
|
||||
# ステートレス単一インスタンス規約へ移行し、認証を申請する場合のみ追加:
|
||||
# sharedRuntime: shared-runtime-v1
|
||||
# componentModel: stateless-v1
|
||||
```
|
||||
|
||||
認証を申請し、複数のインストールで 1 つのランタイムを共有する場合は、`execution` 配下に `sharedRuntime` と `componentModel` も宣言します。詳細は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
プラグインの多言語サポートは[RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646)標準に従います。現在サポートされている言語は次のとおりです:
|
||||
|
||||
- `en_US` 英語(必須)
|
||||
|
||||
@@ -78,14 +78,8 @@ Plugin built successfully: dist/tmp/RockChinQ-HelloPlugin-0.1.0.lbpkg
|
||||
|
||||
[LangBotプラグインマーケットプレイス](https://space.langbot.app/market)でプラグイン提出タスクを確認できます。承認をお待ちください。承認後、マーケットプレイスでプラグインを表示できます。
|
||||
|
||||
## 認証プラグイン審査を申請する
|
||||
|
||||
認証を申請するリリースでは、`manifest.yaml` に `execution.sharedRuntime: shared-runtime-v1` と `execution.componentModel: stateless-v1` を同時に宣言し、**新しいバージョン**を提出します。補助レビューでは、単一インスタンスのコンポーネントがステートレスかつ再入可能で、呼び出しごとに設定を取得し、テナントデータを保持しないことを確認します。宣言だけで認証や共有実行権限が付与されることはありません。
|
||||
|
||||
LangBot Space は提出された正確なパッケージを確認し、補助的なコードレビューを実行したうえで、人による承認後にのみ認証プラグインの表示を発行します。認証は審査済みのパッケージバージョンとチェックサムに紐付きます。ワークスペース設定、ストレージ、資格情報、ログ、バックグラウンド処理は、現在のインストールに必ず紐付けてください。可変なプロセスグローバル状態に依存しないでください。
|
||||
|
||||
要件を満たせない場合は署名なしの通常の専用プラグインとして提出し、後続の不変バージョンで修正してください。専用実行の認証や中間の認証段階はありません。ユーザー向けの説明は[認証プラグイン](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
<img width="600" src="/images/zh/plugin/dev/plugin_submission.png" />
|
||||
|
||||
公開の流れ自体に認証は関係しません。プラグインは既定で専用モードで動作します。このバージョンで認証も申請する場合は、[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
ご質問がある場合は、`[email protected]`にメールを送信するか、[コミュニティ](/ja/insight/community)を通じてお問い合わせください。
|
||||
|
||||
+157
-28
@@ -1,60 +1,189 @@
|
||||
---
|
||||
title: "认证插件与无状态组件"
|
||||
description: "认证插件为什么共享 Worker,以及 stateless-v1 组件规范。"
|
||||
title: "认证插件与共享运行"
|
||||
description: "认证是什么、为什么存在、什么时候需要,以及 shared-runtime-v1 / stateless-v1 的适配规范。"
|
||||
---
|
||||
|
||||
认证只代表精确、不可变的插件制品可在 LangBot Cloud 跨租户共享,不是一般的质量或安全徽章。安装的制品拥有有效可信签名后必须以 shared 模式运行,不得回退到 dedicated。市场上的**认证插件**标识本身不能证明下载的 ZIP 已通过验证,也不能证明线上 Worker 实际共享。相同 artifact digest 的安装共享一个 Worker、一个 `BasePlugin` 实例和一组组件实例;每个安装只保留轻量的配置、权限与生命周期上下文。这里的“一组组件”指每个已声明组件 manifest 各一个实例,包括 EventListener、Tool、Command、KnowledgeEngine、Parser、Page 和 Runner。
|
||||
## 认证是什么
|
||||
|
||||
## 声明
|
||||
认证是 LangBot 对**一份精确、不可变的插件制品**授予的资格:该制品被批准在 LangBot Cloud 中**跨租户共享运行**。
|
||||
|
||||
- 认证绑定到制品本身(已发布的具体版本,按 digest 区分),不是绑定到插件名或作者。同一个插件发布新版本后,上一版的认证不会自动延续。
|
||||
- 认证不是质量评分,也不是安全审计背书。
|
||||
|
||||
## 为什么需要认证
|
||||
|
||||
平台默认**不**让插件共享运行。这是因为共享的粒度是制品:
|
||||
|
||||
- **专属模式**:插件不参与共享,独立运行。
|
||||
- **共享模式**:同一个 artifact digest 的所有安装共用一个 Worker。
|
||||
|
||||
共享的好处是进程数量和启动开销不随安装数线性增长。代价是**一个进程里会同时处理多个工作区的调用**——如果插件把租户数据放在实例上,就会在租户之间串数据。
|
||||
|
||||
所以平台需要先对这份制品做代码审查、确认它满足无状态约束,再用签名把"对这份制品的信任"固定下来,才敢让它进入共享放置。这就是认证存在的原因。
|
||||
|
||||
## 什么情况下需要认证
|
||||
|
||||
**大多数插件不需要认证。** 不认证不影响你把插件发布到插件市场,也不影响用户安装使用——插件会以专属模式正常运行。
|
||||
|
||||
| 你的情况 | 是否需要认证 |
|
||||
| --- | --- |
|
||||
| 只想把插件发布到插件市场供用户使用 | 不需要 |
|
||||
| 插件需要在进程内保留跨调用状态(例如常驻的外部会话进程) | 不需要,也应该保持专属运行 |
|
||||
| 插件里有后台任务、定时器,或长期缓存租户数据 | 不需要,保持专属模式更安全 |
|
||||
| 希望获得认证插件标识 | 需要 |
|
||||
| 希望同一制品的多个安装共用一个进程,降低资源与启动开销 | 需要 |
|
||||
| 插件面向 Cloud 多租户场景,希望被平台以共享方式放置 | 需要 |
|
||||
|
||||
判断标准只有一条:**只有当你的代码确实能做到"实例上不保存任何租户或安装相关数据"时,认证才有意义。**
|
||||
|
||||
## 认证之后的效果
|
||||
|
||||
通过审核后:
|
||||
|
||||
- 制品被签入证书,安装时以**共享模式**运行;同一 digest 的多个安装共用一个 Worker。
|
||||
- 该 Worker 中只有一个 `BasePlugin` 实例,以及每个已声明组件 manifest 各一个实例(EventListener、Tool、Command、KnowledgeEngine、Parser、Page、Runner)。
|
||||
- 每个安装只额外保留一份轻量的配置、权限与生命周期上下文,调用时由当前请求决定使用哪一份。
|
||||
- 你会获得认证插件标识。
|
||||
- 自托管 OSS 默认不配置认证签发方公钥,因此即使是带认证的制品,在自托管实例上仍以专属模式运行。
|
||||
|
||||
同时你也承担一项长期义务:**后续每个版本都必须继续满足无状态约束**。修改代码后需要提交新版本重新审核,认证不会自动延续。
|
||||
|
||||
## 需要做的适配与规范
|
||||
|
||||
### 声明
|
||||
|
||||
在 `manifest.yaml` 的 `execution` 下同时声明两个字段:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Plugin
|
||||
metadata:
|
||||
author: your-name
|
||||
name: YourPlugin
|
||||
version: 0.1.1
|
||||
spec:
|
||||
config: []
|
||||
components: {}
|
||||
execution:
|
||||
python:
|
||||
path: main.py
|
||||
attr: Plugin
|
||||
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 会忽略它们,因此不要用旧版本验证声明是否生效。
|
||||
|
||||
- `BasePlugin.initialize()` 和组件 `initialize()` 是进程级初始化,每个 Worker 只运行一次。
|
||||
- 所有组件实例都可能同时处理不同工作区的调用,必须可重入。
|
||||
- 在 `BasePlugin` 中使用 `self.get_config()`;在组件中使用 `self.get_plugin_config()` 或 `self.plugin.get_config()` 读取当前安装配置。
|
||||
- 不得在实例字段或模块全局变量中保存配置、凭据、Workspace/installation ID 或请求数据。
|
||||
- 不得在 `initialize()` 中读取并保存租户配置。
|
||||
- 存储、文件、凭据、日志、模型及其他平台能力必须通过 SDK Host API 访问;SDK 从当前 task-local invocation context 取得权限。
|
||||
- 持久租户状态必须保存在 Host 服务中。进程内 cache 只能保存 artifact 级公共数据,或以完整 installation binding 为 key 并支持撤销清理。
|
||||
- `stateless-v1` 当前禁止携带租户数据的 detached 后台任务或线程;SDK 尚未提供插件侧所有权/撤销 API。
|
||||
<Warning>
|
||||
声明只是申请,不等于通过认证。它本身不会授予认证标识,也不会改变运行模式。
|
||||
</Warning>
|
||||
|
||||
这些约束为未来 serverless 组件执行保留接口语义;当前版本仍运行长生命周期 Worker,并未提供 serverless 部署模式。
|
||||
### 无状态组件规范
|
||||
|
||||
## 配置访问示例
|
||||
- `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`)。进程内缓存只允许存放制品级公共数据;如果确实要缓存租户数据,键必须包含完整安装标识,并能在安装被移除时清理。
|
||||
- 禁止发起携带租户数据的后台任务或线程。处理函数返回后,本次调用的上下文和运行关联调用即失效。
|
||||
|
||||
### 允许 / 禁止对照
|
||||
|
||||
| 场景 | 允许 | 禁止 |
|
||||
| --- | --- | --- |
|
||||
| 读取配置 | 每次调用时读取(`self.get_plugin_config()`、`ctx.config`) | 在 `initialize()` 中读取后保存到 `self` |
|
||||
| 连接池 / HTTP 客户端 | 不含租户凭据的共享连接池 | 把安装的 endpoint、api key 固化到实例上 |
|
||||
| 缓存 | 制品级公共数据;或键为完整安装标识且支持清理 | 模块级 `dict` 以工作区或用户为键长期驻留 |
|
||||
| 跨调用状态 | Host 服务(Runner 用 `ctx.state_*`) | 实例字段、模块级全局变量 |
|
||||
| 后台工作 | 无 | 处理函数返回后继续持有本次上下文或其关联调用 |
|
||||
| 日志 | `ctx.log()` 或 SDK 日志 API | 模块级后台线程直接输出租户配置或凭据 |
|
||||
|
||||
### 常见反例
|
||||
|
||||
**1. 在初始化时读取安装配置**
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.endpoint = self.plugin.config["endpoint"] # 进程级初始化,没有当前安装
|
||||
self.api_key = self.plugin.config["api_key"]
|
||||
```
|
||||
|
||||
共享初始化阶段没有安装配置,这段代码只会读到空值或上一次的值。
|
||||
|
||||
**2. 把租户数据放进模块级缓存**
|
||||
|
||||
```python
|
||||
_CACHE = {} # 以工作区或用户为键,会跨安装、跨租户长期驻留
|
||||
|
||||
async def call(self, params, **kwargs):
|
||||
if params["user"] not in _CACHE:
|
||||
_CACHE[params["user"]] = await fetch(params["user"])
|
||||
```
|
||||
|
||||
**3. 处理函数返回后继续使用本次调用**
|
||||
|
||||
```python
|
||||
async def call(self, params, **kwargs):
|
||||
config = self.get_plugin_config()
|
||||
asyncio.create_task(self._upload_later(config)) # 处理函数已返回,上下文与授权均已失效
|
||||
return ok()
|
||||
```
|
||||
|
||||
**4. 把实例字段当作配置容器**
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.default_endpoint = "https://api.example.com" # 公共常量没有问题
|
||||
self.last_config = {} # 一旦写入安装配置就违规
|
||||
```
|
||||
|
||||
正确写法:
|
||||
|
||||
```python
|
||||
class LookupTool(Tool):
|
||||
async def initialize(self):
|
||||
# 只放置制品级、不含租户信息的对象
|
||||
self.transport = StatelessHTTPTransport()
|
||||
|
||||
async def call(self, params, **kwargs):
|
||||
config = self.get_plugin_config()
|
||||
config = self.get_plugin_config() # 每次调用按当前安装读取
|
||||
return await self.transport.lookup(config["endpoint"], params)
|
||||
```
|
||||
|
||||
不要这样写:
|
||||
### Runner 组件特别注意
|
||||
|
||||
```python
|
||||
async def initialize(self):
|
||||
self.endpoint = self.plugin.config["endpoint"]
|
||||
self.api_key = self.plugin.config["api_key"]
|
||||
```
|
||||
Runner 与无状态约束的张力最大,因为运行器天然倾向于在进程内保存会话与上下文。
|
||||
|
||||
共享初始化没有租户配置,因此这段代码会读取空值/旧值或失败;任何把调用配置保存到共享对象的做法都是认证 blocker。
|
||||
- `RunnerContext` **只在本次运行期间有效**,不要把它或它的字段保存到实例上跨调用复用。
|
||||
- Runner 需要跨调用保留的会话状态,走 `ctx.state_get()` / `ctx.state_set()`(作用域支持 `conversation`、`actor`、`subject`、`runner`)或 Host 提供的其他存储,不要使用实例字段。
|
||||
- 处理函数返回后,不要继续在后台任务中使用 `ctx`,也不要继续使用通过 `self.plugin` 发起的运行关联调用。
|
||||
- 如果 Runner 的设计必须依赖进程内持有的跨调用状态(例如需要长期驻留的外部会话进程),**不要声明 `stateless-v1`**,继续以专属 Worker 运行即可。
|
||||
|
||||
## 兼容旧插件
|
||||
运行上下文的完整接口见[运行上下文(RunnerContext)](/zh/plugin/dev/apis/agent-run)。
|
||||
|
||||
旧源码和未签名插件包仍兼容专属 Worker,Cloud 也允许未签名专属安装。不存在专属运行认证或专属签名。Cloud 必须拒绝畸形、伪造、不可信或缺少 `stateless-v1` 声明的旧版签名证书,不能将其当作未签名制品。自托管 OSS 不内置认证签发方公钥,仅支持单工作区;不支持配置公钥获得认证共享运行。已有安装或市场标识不代表获得共享资格。仅修改 manifest 不会通过认证审查。
|
||||
## 自检与提交
|
||||
|
||||
认证流程仍包括辅助代码审查和人工批准,并将插件身份、版本、digest、`shared-runtime-v1` 与 `stateless-v1` 一起签入证书。提交方式参阅[发布到插件市场](/zh/plugin/dev/publish/market)。
|
||||
提交审核前逐条核对:
|
||||
|
||||
- [ ] `manifest.yaml` 中两个字段都已声明,取值正确。
|
||||
- [ ] 全文搜索,确认没有模块级可变全局变量。
|
||||
- [ ] 确认 `initialize()` 只做进程级初始化,不读取安装配置。
|
||||
- [ ] 确认所有安装配置都在调用时读取。
|
||||
- [ ] 确认没有在处理函数返回后继续运行的任务或线程。
|
||||
- [ ] 确认跨调用状态通过 Host API 保存(Runner 使用 `ctx.state_*`)。
|
||||
|
||||
### 提交方式
|
||||
|
||||
认证与发布走同一条链路,不需要额外流程:在 `manifest.yaml` 中声明上面两个字段,然后照常用 `lbp publish` 提交一个**新版本**(已发布的版本无法追加声明)。LangBot Space 会检查本次提交的精确插件包,进行辅助代码审查,并在人工批准后签发认证插件标识;认证绑定到已审核的版本与校验和。
|
||||
|
||||
如果审核发现该版本无法满足要求,请先把它作为未签名的普通插件发布,并在后续不可变版本中修复问题。
|
||||
|
||||
如果核对下来发现插件并不适合共享运行,直接以普通插件提交即可,不需要认证。发布流程见[发布到插件市场](/zh/plugin/dev/publish/market)。
|
||||
|
||||
@@ -28,12 +28,11 @@ execution:
|
||||
python:
|
||||
path: main.py # 请勿修改
|
||||
attr: HelloPlugin # 请勿修改
|
||||
# 仅在申请认证且代码已迁移为无状态单例时添加:
|
||||
# sharedRuntime: shared-runtime-v1
|
||||
# componentModel: stateless-v1
|
||||
```
|
||||
|
||||
插件多语言遵循 [RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646) 标准,目前支持的语言有:
|
||||
如果需要申请认证并让多个安装共享运行,还需要在 `execution` 下声明 `sharedRuntime` 与 `componentModel`,见[认证插件与共享运行](/zh/plugin/certified-plugins)。
|
||||
|
||||
插件多语言遵循 [RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646) 标准。目前支持的语言有:
|
||||
|
||||
- `en_US` 英文 (必填)
|
||||
- `zh_Hans` 简体中文
|
||||
|
||||
@@ -78,14 +78,8 @@ Plugin built successfully: dist/tmp/RockChinQ-HelloPlugin-0.1.0.lbpkg
|
||||
|
||||
您现在可以在[LangBot 插件市场](https://space.langbot.app/market)查看您的插件提交任务。请等待审核通过后,您可以在插件市场查看您的插件。
|
||||
|
||||
## 申请认证插件审核
|
||||
|
||||
如果该版本准备申请认证,请在 `manifest.yaml` 中同时声明 `execution.sharedRuntime: shared-runtime-v1` 和 `execution.componentModel: stateless-v1`,并提交一个**新版本**。辅助审查会确认单例组件无状态、可重入、按调用读取配置且不持有租户数据。声明本身不会授予认证标识或共享运行权限。
|
||||
|
||||
LangBot Space 会检查本次提交的精确插件包,进行辅助代码审查,并在人工批准后才签发认证插件标识。认证绑定到已审核的插件版本与校验和。请让工作区配置、存储、凭据、日志和后台工作都与当前安装绑定,不要依赖可变的进程全局状态。
|
||||
|
||||
如果审核发现该版本无法满足要求,请先作为未签名的普通专属插件提交,并在后续不可变版本中修复问题。不存在专属运行认证或中间认证等级。用户侧说明见[认证插件](/zh/plugin/certified-plugins)。
|
||||
|
||||
<img width="600" src="/images/zh/plugin/dev/plugin_submission.png" />
|
||||
|
||||
发布流程本身不涉及认证:插件默认以专属模式运行。如果这个版本还要申请认证,见[认证插件与共享运行](/zh/plugin/certified-plugins)。
|
||||
|
||||
有任何问题,您可以发送邮件到`[email protected]`或通过[社区](/zh/insight/community)联系我们。
|
||||
|
||||
Reference in New Issue
Block a user