Merge pull request #62 from langbot-app/feat/new-plugin

Feat/new plugin
This commit is contained in:
Junyan Qin (Chin)
2025-09-16 12:35:23 +08:00
committed by GitHub
parent 61eca6f343
commit 5a3cd5fcb1
59 changed files with 3246 additions and 1390 deletions
-164
View File
@@ -1,164 +0,0 @@
# API 参考
:::info 目录
[[toc]]
:::
## 事件 API
以下 API 仅在事件处理函数中可用。
### 回复消息
```python
await ctx.reply(message_chain: MessageChain)
```
回复此次事件的发起会话。
- `message_chain`:[消息链对象](./messages.md),程序能自动转换为目标消息平台消息链
### 发送主动消息
> 由于 QQ 官方 API 对主动消息的支持性很差,故若用户使用的是 QQ 官方 API,发送主动消息可能会失败
```python
await ctx.send_message(target_type: str, target_id: str, message_chain: MessageChain)
```
发送主动消息给目标。
- `target_type`:目标类型,可选值:`"person"`、`"group"`
- `target_id`:目标 ID(QQ 号或群号)
- `message_chain`:[消息链对象](./messages.md),程序能自动转换为目标消息平台消息链
### 阻止事件默认行为
```python
ctx.prevent_default()
```
阻止此次事件的默认行为即停止处理流程之后的行为(如私聊收到消息时向接口获取回复、群消息收到消息时向接口获取回复等)。
### 阻止后续插件执行
```python
ctx.prevent_postorder()
```
阻止此次事件后续插件的执行。插件的执行顺序请通过`插件介绍`中的`插件管理`介绍的方式修改优先级。
### 添加返回值
```python
ctx.add_return(name: str, value: Any)
```
添加返回值,事件返回值均为**可选**的,每个事件支持的返回值请查看`pkg.plugin.events`中的每个事件的注释。
## 请求 API
`请求(Query)`指的是用户向 LangBot 发送一个问题时,LangBot 处理该问题的程序上下文。此段落的 API 用于与这些上下文交互。
- 事件处理函数中,Query 对象一般位于`ctx.event.query`
### 设置请求变量
```python
ctx.event.query.set_variable(key: str, value: typing.Any)
```
请求变量是与此次 Query 绑定的一个 `dict`,其中包含了一些程序上下文信息。如果使用的是 Dify 或者 阿里云百炼 等 LLMOps 的runner,[这些变量将被传入对应平台的 API 作为变量](/zh/deploy/pipelines/readme.html#%E5%AF%B9%E8%AF%9D%E5%8F%98%E9%87%8F)。
如果您需要使用插件设置变量,建议在`PromptPreProcessing`事件时处理。
- `key`:该变量的名称
- `value`:该变量的值
### 获取请求变量
```python
ctx.event.query.get_variable(key: str)
```
获取请求变量。
- `key`:变量名称
- 返回值:`typing.Any`
### 获取所有请求变量
```python
ctx.event.query.get_variables()
```
获取此 Query 对象所有已设置的变量值。
- 返回值:`dict[str, typing.Any]`
## LangBot API
以下 API 是 LangBot 直接提供给插件调用的,可以在事件处理函数之外调用。
- `host` 表示 `pkg.plugin.context.APIHost` 类的对象,会被包含在每个插件类中。
### 获取已启用的消息平台适配器列表
```python
host.get_platform_adapters()
```
获取已启用的消息平台适配器列表。
- 返回值:`list[pkg.platform.adapter.MessageSourceAdapter]`
### 发送主动消息
```python
await host.send_active_message(adapter: pkg.platform.adapter.MessageSourceAdapter, target_type: str, target_id: str, message_chain: MessageChain)
```
发送主动消息给目标。
- `adapter`:消息平台适配器,通过 `host.get_platform_adapters()` 获取并选择其中一个
- `target_type`:目标类型,可选值:`"person"`、`"group"`
- `target_id`:目标 ID(QQ 号或群号)
- `message_chain`:[消息链对象](./messages.md),程序能自动转换为目标消息平台消息链
:::info
- 某些消息平台可能不支持主动消息,或有严格限制
- 某些消息平台适配器(例如`aiocqhttp`)是作为服务端等待消息平台连接上来推送消息,在连接成功之前,发送主动消息会失败
:::
示例:
```python
import asyncio
import pkg.platform.types as platform_types
...
# 某个插件的 initialize 函数
# 此代码将在 LangBot 启动 10 秒后尝试向 QQ 号 1010553892 发送消息 "hello, world!"
async def initialize(self):
print(self.host.get_platform_adapters())
async def send_message():
print("send message start waiting")
await asyncio.sleep(10)
try:
await self.host.send_active_message(
adapter=self.host.get_platform_adapters()[0],
target_type="person",
target_id="1010553892",
message=platform_types.MessageChain([
platform_types.Plain("hello, world!")
])
)
except Exception as e:
traceback.print_exc()
print("send message end")
asyncio.get_running_loop().create_task(send_message())
```
+300
View File
@@ -0,0 +1,300 @@
# 插件通用 API
LangBot 为插件提供了一系列 API ,用于操作 LangBot 各个模块、控制消息上下文等。
:::info 目录
[[toc]]
:::
## 请求 API
操作当前正在处理的用户请求(消息)。仅在`EventListener`和`Command`组件中可用。访问方式如下:
- `EventListener` 的各个事件处理器方法中:`event_context: context.EventContext`对象内部方法
- `Command` 的各个子命令处理器方法中:`context: ExecuteContext`对象内部方法
### 直接回复消息
直接回复一个消息链到当前请求所在会话。
消息链构造方式请参考[消息平台实体](/zh/plugin/dev/apis/messages)。
```python
async def reply(
self, message_chain: platform_message.MessageChain, quote_origin: bool = False
):
"""Reply to the message request
Args:
message_chain (platform.types.MessageChain): LangBot message chain
quote_origin (bool): Whether to quote the original message
"""
# 调用示例
await event_context.reply(
platform_message.MessageChain([
platform_message.Plain(text="Hello, world!"),
]),
)
```
### 获取机器人 UUID
获取当前请求来源机器人 UUID。
```python
async def get_bot_uuid(self) -> str:
"""Get the bot uuid"""
# 调用示例
bot_uuid = await event_context.get_bot_uuid()
```
### 设置请求变量
单次请求中某些信息会被存储到请求变量中,在使用 Dify 等外部 LLMOps 平台时,[这些变量会显式传入 LLMOps 平台](/zh/deploy/pipelines/readme.html#%E8%AF%B7%E6%B1%82%E5%8F%98%E9%87%8F)。
```python
async def set_query_var(self, key: str, value: Any):
"""Set a query variable"""
# 调用示例
await event_context.set_query_var("key", "value")
```
### 获取请求变量
获取单个请求变量。
```python
async def get_query_var(self, key: str) -> Any:
"""Get a query variable"""
# 调用示例
query_var = await event_context.get_query_var("key")
```
### 获取所有请求变量
```python
async def get_query_vars(self) -> dict[str, Any]:
"""Get all query variables"""
# 调用示例
query_vars = await event_context.get_query_vars()
```
## LangBot API
这些 API 可在插件任何组件中调用。访问方式如下:
- 插件根目录`main.py`中:`self` 对象内部方法,这些 API 均由插件类父类 `BasePlugin` 提供。
- 插件任何组件类中:`self.plugin` 对象内部方法。
### 获取插件配置信息
插件配置格式可在`manifest.yaml`中编写,用户需要在 LangBot 的插件管理中按照插件配置格式填写。之后插件代码可以调用此 API 获取插件配置信息。
```python
def get_config(self) -> dict[str, typing.Any]:
"""Get the config of the plugin."""
# 调用示例
config = self.plugin.get_config()
```
### 获取 LangBot 版本
获取 LangBot 版本号,返回格式为字符串`v<major>.<minor>.<patch>`。
```python
async def get_langbot_version(self) -> str:
"""Get the langbot version"""
# 调用示例
langbot_version = await self.plugin.get_langbot_version()
```
### 获取已配置的机器人列表
返回由所有机器人 UUID 组成的列表。
```python
async def get_bots(self) -> list[str]:
"""Get all bots"""
# 调用示例
bots = await self.plugin.get_bots()
```
### 获取机器人信息
获取机器人信息。
```python
async def get_bot_info(self, bot_uuid: str) -> dict[str, Any]:
"""Get a bot info"""
# 调用示例
bot_info = await self.plugin.get_bot_info("de639861-be05-4018-859b-c2e2d3e0d603")
# 返回示例
{
"uuid": "de639861-be05-4018-859b-c2e2d3e0d603",
"name": "aiocqhttp",
"description": "由 LangBot v3 迁移而来",
"adapter": "aiocqhttp",
"enable": true,
"use_pipeline_name": "ChatPipeline",
"use_pipeline_uuid": "c30a1dca-e91c-452b-83ec-84d635a30028",
"created_at": "2025-05-10T13:53:08",
"updated_at": "2025-08-12T11:27:30",
"adapter_runtime_values": { # 若该机器人正在运行中,则具有该字段
"bot_account_id": 960164003 # 机器人账号 ID
}
}
```
### 发送主动消息
通过机器人 UUID 和目标会话 ID 发送主动消息。
消息链构造方式请参考[消息平台实体](/zh/plugin/dev/apis/messages)。
```python
async def send_message(
self,
bot_uuid: str,
target_type: str,
target_id: str,
message_chain: platform_message.MessageChain,
) -> None:
"""Send a message to a session"""
# 调用示例
await self.plugin.send_message(
bot_uuid="de639861-be05-4018-859b-c2e2d3e0d603",
target_type="person",
target_id="1010553892",
message_chain=platform_message.MessageChain([platform_message.Plain(text="Hello, world!")]),
)
```
### 获取已配置的 LLM 模型列表
返回由所有已配置的 LLM 模型 UUID 组成的列表。
```python
async def get_llm_models(self) -> list[str]:
"""Get all LLM models"""
# 调用示例
llm_models = await self.plugin.get_llm_models()
```
### 调用 LLM 模型
调用 LLM 模型,返回 LLM 消息。非流式。
```python
async def invoke_llm(
self,
llm_model_uuid: str,
messages: list[provider_message.Message],
funcs: list[resource_tool.LLMTool] = [],
extra_args: dict[str, Any] = {},
) -> provider_message.Message:
"""Invoke an LLM model"""
# 调用示例
llm_message = await self.plugin.invoke_llm(
llm_model_uuid="llm_model_uuid",
messages=[provider_message.Message(role="user", content="Hello, world!")],
funcs=[],
extra_args={},
)
```
### 设置插件持久化数据
持久化存储插件数据。通过这个接口存储的数据,仅可被该插件访问。值需要自行转换为字节。
```python
async def set_plugin_storage(self, key: str, value: bytes) -> None:
"""Set a plugin storage value"""
# 调用示例
await self.plugin.set_plugin_storage("key", b"value")
```
### 获取插件持久化数据
```python
async def get_plugin_storage(self, key: str) -> bytes:
"""Get a plugin storage value"""
# 调用示例
plugin_storage = await self.plugin.get_plugin_storage("key")
```
### 获取插件所有持久化数据键
```python
async def get_plugin_storage_keys(self) -> list[str]:
"""Get all plugin storage keys"""
# 调用示例
plugin_storage_keys = await self.plugin.get_plugin_storage_keys()
```
### 删除插件持久化数据
```python
async def delete_plugin_storage(self, key: str) -> None:
"""Delete a plugin storage value"""
# 调用示例
await self.plugin.delete_plugin_storage("key")
```
### 获取工作空间持久化数据
通过这个接口存储的数据,可被所有插件访问。值需要自行转换为字节。
```python
async def set_workspace_storage(self, key: str, value: bytes) -> None:
"""Set a workspace storage value"""
# 调用示例
await self.plugin.set_workspace_storage("key", b"value")
```
### 获取工作空间持久化数据
```python
async def get_workspace_storage(self, key: str) -> bytes:
"""Get a workspace storage value"""
# 调用示例
workspace_storage = await self.plugin.get_workspace_storage("key")
```
### 获取工作空间所有持久化数据键
```python
async def get_workspace_storage_keys(self) -> list[str]:
"""Get all workspace storage keys"""
# 调用示例
workspace_storage_keys = await self.plugin.get_workspace_storage_keys()
```
### 删除工作空间持久化数据
```python
async def delete_workspace_storage(self, key: str) -> None:
"""Delete a workspace storage value"""
# 调用示例
await self.plugin.delete_workspace_storage("key")
```
@@ -4,45 +4,41 @@
[[toc]]
:::
LangBot 支持多种消息平台,但每个消息平台的`消息实体`格式均不相同,为了屏蔽这些差异,LangBot 具有一套统一的标准。插件开发者只需要掌握并在插件中使用此页所述的消息实体,LangBot 内部的消息处理逻辑能够自动完成消息的解析和转换。
:::info
LangBot 的消息平台实体是基于 [YiriMirai](https://github.com/YiriMiraiProject/YiriMirai) 的实现改造的。
:::
LangBot 支持多种消息平台,每个消息平台的`消息实体`格式均不相同。为了屏蔽这些差异,我们实现了一套统一的标准。插件开发者只需要掌握并在插件中使用此页所述的消息实体,LangBot 内部的消息处理逻辑能够自动完成消息的解析和转换。
## 消息链
消息平台的`消息`不同于用于 AI 交互的消息,消息平台的消息以`消息链`的形式描述,QQ 上展示的每一个独立的消息就是一条消息链,消息链中可以包含`文字`、`图片`、`@组件`等多种`消息链组件`,例如:
![](/assets/image/zh/plugin/dev/plugin_dev_messages_01.png)
![](/assets/image/zh/plugin/dev/apis/plugin_dev_messages_01.png)
这就是一个消息链,包含 一个 Plain 组件(Hello World)和 一个 Image 组件(乌萨奇)
消息链和消息链组件的定义位于`pkg/platform/types/messages.py`中。
消息链和消息链组件的定义位于`langbot_plugin.api.entities.platform.message`中。
### 构造消息链
请先引入`pkg.platform.types`包,才能使用其中的消息组件。
请先引入`langbot_plugin.api.entities.platform.message`包,才能使用其中的消息组件。
```python
from pkg.platform.types import *
from langbot_plugin.api.entities.platform.message import *
# 构建一个包含文字 Hello LangBot 和图片(从URL获取)的消息
msg_chain = MessageChain([
Plain("Hello LangBot"),
Plain(text="Hello LangBot"),
Image(url='https://qchatgpt.rockchin.top/langbot-logo.png')
])
# 构建一个包含 @全体成员 和文字 Hello LangBot 的消息
msg_chain = MessageChain([
AtAll(),
Plain("Hello LangBot")
Plain(text="Hello LangBot")
])
# 构建一个包含 @指定成员 和文字 Hello LangBot 的消息
msg_chain = MessageChain([
At(123456),
Plain("Hello LangBot")
At(target=123456),
Plain(text="Hello LangBot")
])
```
@@ -60,29 +56,18 @@ msg_chain = MessageChain([
- 在很多平台不受支持,不建议使用
- `File` 文件消息
具体使用方式可以查看源码`pkg/platform/types/messages.py`中的定义。
## 消息平台事件
这些事件不同于 LangBot 插件事件,是消息平台系统独立的一套事件系统。这些事件是 LangBot 消息的源事件,可以从每个 Query.message_event 取得。
```python
# 例如在插件中
@handler(GroupMessageReceived)
async def _(self, ctx: EventContext):
# 取得消息平台事件
message_event = ctx.query.message_event
```
消息平台事件定义位于`pkg/platform/types/events.py`中。
创建组件时,须使用具名参数传入。具体使用方式可以查看源码`langbot_plugin.api.entities.platform.message`中的定义。
## 其他实体
除了上述实体之外,还有`好友 Friend`和`群 Group`等实体,这些实体的定义位于`pkg/platform/types/entities.py`中。
他们一般被包含于上述事件对象中,可以从中取得一些未被包含在插件事件中的信息。
除了上述实体之外,还有`好友 Friend`和`群 Group`等实体,这些实体的定义位于`langbot_plugin.api.entities.platform.entities`中。
## 访问消息平台底层 API
:::warning 注意
4.x 暂不适用。
:::
为了抹平各个平台的差异,LangBot 在消息平台API之上,提供了一个抽象层,即上方所述的内容,以及`pkg/platform/sources`目录下的各个平台适配器。
但由于各个平台的差异较大,抽象层内的实体和 API 封装并不完整,如果您的插件需要访问特定平台的功能,可以按照以下说明访问底层 API。
+218
View File
@@ -0,0 +1,218 @@
# 流水线事件
:::info 目录
[[toc]]
:::
LangBot 插件可注册并处理流水线事件。使用方法见[组件:事件监听器](/zh/plugin/dev/components/event-listener)。
## *MessageReceived
接收到群聊或私聊任何消息时触发。
```python
class PersonMessageReceived(BaseEventModel):
"""收到任何私聊消息时"""
event_name: str = "PersonMessageReceived"
launcher_type: str
"""发起对象类型(person)"""
launcher_id: typing.Union[int, str]
"""发送者ID"""
sender_id: typing.Union[int, str]
"""发送者ID"""
message_chain: platform_message.MessageChain = pydantic.Field(
serialization_alias="message_chain"
)
class GroupMessageReceived(BaseEventModel):
"""收到任何群聊消息时"""
event_name: str = "GroupMessageReceived"
launcher_type: str
"""发起对象类型(group)"""
launcher_id: typing.Union[int, str]
"""群号"""
sender_id: typing.Union[int, str]
"""发送者ID"""
message_chain: platform_message.MessageChain = pydantic.Field(
serialization_alias="message_chain"
)
```
## *NormalMessageReceived
接收到群聊或私聊消息,并判断为需要由 LLM 处理的消息(非命令消息)时触发。
```python
class PersonNormalMessageReceived(BaseEventModel):
"""判断为应该处理的私聊普通消息时触发"""
event_name: str = "PersonNormalMessageReceived"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
text_message: str
"""消息文本"""
alter: typing.Optional[str] = None
"""修改后的消息文本"""
reply: typing.Optional[list] = None
"""回复消息组件列表"""
class GroupNormalMessageReceived(BaseEventModel):
"""判断为应该处理的群聊普通消息时触发"""
event_name: str = "GroupNormalMessageReceived"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
text_message: str
"""消息文本"""
alter: typing.Optional[str] = None
"""修改后的消息文本"""
reply: typing.Optional[list] = None
"""回复消息组件列表"""
```
## *CommandSent
:::warning 注意
不再推荐使用,请改为使用[组件:命令](/zh/plugin/dev/components/command)。
:::
接收到群聊或私聊命令时触发。
```python
class PersonCommandSent(BaseEventModel):
"""判断为应该处理的私聊命令时触发"""
event_name: str = "PersonCommandSent"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
command: str
"""命令文本"""
params: list[str]
"""命令参数"""
text_message: str
"""消息文本"""
is_admin: bool
"""是否为管理员"""
alter: typing.Optional[str] = None
"""修改后的完整命令文本"""
reply: typing.Optional[list] = None
"""回复消息组件列表"""
class GroupCommandSent(BaseEventModel):
"""判断为应该处理的群聊命令时触发"""
event_name: str = "GroupCommandSent"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
command: str
"""命令文本"""
params: list[str]
"""命令参数"""
text_message: str
"""消息文本"""
is_admin: bool
"""是否为管理员"""
alter: typing.Optional[str] = None
"""修改后的完整命令文本"""
reply: typing.Optional[list] = None
"""回复消息组件列表"""
```
## NormalMessageResponded
消息得到 LLM 响应时触发。
```python
class NormalMessageResponded(BaseEventModel):
"""回复普通消息时触发"""
event_name: str = "NormalMessageResponded"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
session: provider_session.Session
"""会话对象"""
prefix: str
"""回复消息的前缀"""
response_text: str
"""回复消息的文本"""
finish_reason: str
"""响应结束原因"""
funcs_called: list[str]
"""调用的函数列表"""
reply: typing.Optional[list] = None
"""回复消息组件列表"""
```
## PromptPreProcessing
在构建 LLM 响应的上下文(prompt)时触发。
```python
class PromptPreProcessing(BaseEventModel):
"""会话中的Prompt预处理时触发"""
event_name: str = "PromptPreProcessing"
session_name: str
default_prompt: list[provider_message.Message]
"""此对话的情景预设,可修改"""
prompt: list[provider_message.Message]
"""此对话现有消息记录,可修改"""
```
+11
View File
@@ -0,0 +1,11 @@
# 插件技术细节
## 架构
<img width="600" src="/assets/image/zh/plugin/dev/plugin_system_arch.png" />
在 4.0 版本中,我们引入了一种高安全性、高灵活性的生产级插件系统,并为插件开发者提供了丰富的 API 和易用的配套工具。
Plugin Runtime (插件运行时),用于管理插件的生命周期,协调 LangBot 与插件之间的交互。有两种运行模式:`stdio` 和 `websocket`。当 LangBot 是由用户直接启动(未运行在容器内时),会使用 `stdio` 模式,这种场景多为个人用户或轻量级环境。当 LangBot 运行在容器内时,会使用 `websocket` 模式,此场景专为生产级环境设计。
Plugin Runtime 会自动启动各个已安装的插件,通过 stdio 交互。在插件开发场景中,开发者可以使用 `lbp` 命令行工具启动插件并通过 WebSocket 连接到已启动的 Runtime 进行调试。
+157
View File
@@ -0,0 +1,157 @@
# 完善插件信息
## 修改清单文件
插件目录下的`manifest.yaml`文件声明了插件的基础信息,这些信息会被展示到 LangBot UI 或插件市场等界面上。
```yaml
apiVersion: v1 # 请勿修改
kind: Plugin # 请勿修改
metadata:
author: RockChinQ # 作者,符合正则表达式 ^[a-zA-Z0-9_-]+$
name: HelloPlugin # 插件名称,用于区分插件,符合正则表达式 ^[a-zA-Z0-9-]+$
repository: 'https://github.com/langbot-app/HelloPlugin' # 插件仓库地址
version: 0.1.0 # 插件版本
description:
en_US: 'Hello LangBot Plugin' # 插件描述,多语言
zh_Hans: 'Hello LangBot Plugin'
label:
en_US: HelloPlugin # 插件标签,用于在界面上展示,多语言
zh_Hans: HelloPlugin
icon: assets/icon.svg # 插件图标,默认使用 assets/icon.svg,可自行替换,支持各种图片格式
spec:
config: [] # 插件所需配置项格式
components: {} # 插件组件列表,无需手动修改
execution:
python:
path: main.py # 请勿修改
attr: HelloPlugin # 请勿修改
```
插件多语言遵循 [RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646) 标准,目前支持的语言有:
- `en_US` 英文
- `zh_Hans` 简体中文
- `zh_Hant` 繁体中文
- `ja_JP` 日语
## 插件配置项格式
在 `manifest.yaml` 文件中,`spec.config` 声明的字段将在 LangBot 渲染成配置项表单由用户填写,插件可通过 SDK 提供的 API 获取用户填写的配置项(见后文)。
例如:
```yaml
spec:
config:
- name: github_token # 必填;配置项名称,用于在插件中获取
type: string # 必填;配置项类型,支持 string, integer, float, boolean, select, prompt-editor, llm-model-selector 等
label: # 必填;配置项显示名称,支持多语言。语言代码采用 RFC 4646 标准。
en_US: Github Token
zh_Hans: Github Token
description: # 配置项描述,支持多语言。可选。
en_US: Image downloading requires a Github token
zh_Hans: 如果不填的话,图片可能会下载失败
default: '' # 配置项默认值,可选。
required: false # 配置项是否必填,可选。
- name: mode
type: select
...
```
配置项支持的各个类型及字段如下:
### type: string
字符串。
```yaml
- name: api_key
type: string
...
```
### type: array[string]
字符串数组。
```yaml
- name: tags
type: array[string]
...
```
### type: integer
整数。
```yaml
- name: progress
type: integer
...
```
### type: float
浮点数。
```yaml
- name: temperature
type: float
...
```
### type: boolean
布尔值。
```yaml
- name: is_enabled
type: boolean
...
```
### type: select
下拉框。需要配置 `options` 选项,表示下拉框的选项。
```yaml
- name: mode
type: select
options: # 下拉框选项,支持多语言。
- name: mode1 # 值
label: # 显示名称,支持多语言。
en_US: Mode 1
zh_Hans: 模式 1
- name: mode2
label: # 显示名称,支持多语言。
en_US: Mode 2
zh_Hans: 模式 2
...
```
### type: prompt-editor
提示词编辑器。会展示一个流水线配置页面的提示词编辑器,最终结果表示为 OpenAI 的 `messages` 格式。
```yaml
- name: prompt
type: prompt-editor
...
```
### type: llm-model-selector
LLM 模型选择器。会展示一个 LLM 模型选择器,可选择已配置的 LLM 模型,最终结果表示为 LLM 模型 uuid。
```yaml
- name: model
type: llm-model-selector
...
```
## 接下来做什么
该教程将指引您逐步完善插件功能。
- 添加组件:插件组件是插件的核心功能单元,您可以根据需求[添加组件](/zh/plugin/dev/components/add)。
+72
View File
@@ -0,0 +1,72 @@
# 添加组件
插件由一种或多种组件构成,为 LangBot 提供不同的功能。目前支持的组件类型有:
- 事件监听器(EventListener):监听流水线执行期间的事件,对上下文或流水线进行修改。
- 命令(Command):由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。
- 工具(Tool):供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。
## 添加组件
在插件目录下执行命令:
```bash
lbp comp <component_type>
```
例如,添加一个事件监听器:
```bash
lbp comp EventListener
```
请根据提示输入组件的配置(若有)。
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
➜ HelloPlugin >
```
:::info
CLI 将在插件目录下生成一个 `components` 目录,并在其中创建组件对应目录
```
.
├── assets
│   └── icon.svg
├── components
│   ├── __init__.py
│   └── event_listener
│   ├── __init__.py
│   ├── default.py
│   └── default.yaml
├── main.py
├── manifest.yaml
├── README.md
└── requirements.txt
```
同时,会在插件的`manifest.yaml`中添加组件发现配置:
```yaml
...
components:
EventListener:
fromDirs:
- path: components/event_listener/
...
```
如需删除组件,可自行删除对应信息。
:::
## 组件类型
各个组件的详细使用方式请参考:
- [EventListener](./event-listener.md)
- [Command](./command.md)
- [Tool](./tool.md)
+211
View File
@@ -0,0 +1,211 @@
# 组件:命令
命令组件由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。如下是命令`!help`的触发示例:
<img width="600" src="/assets/image/zh/plugin/dev/components/command_use.png" />
## 添加命令组件
单个插件中能添加任意数量的命令,请在插件目录执行命令`lbp comp Command`,并根据提示输入命令的配置。
```bash
➜ HelloPlugin > lbp comp Command
Generating component Command...
Command name: info
Command description: Show information of the query
Component Command generated successfully.
组件 Command 生成成功。
```
现在即会在`components/commands/`目录下生成`info.yaml`和`info.py`文件,`.yaml`定义了`!info`命令的基础信息,`.py`是该命令的处理程序:
```bash
➜ HelloPlugin > tree
...
├── components
│   ├── __init__.py
│   ├── commands
│   │   ├── __init__.py
│   │   ├── info.py
│   │   └── info.yaml
...
```
## 清单文件:命令组件
```yaml
apiVersion: v1 # 请勿修改
kind: Command # 请勿修改
metadata:
name: info # 命令名称,在使用时,用户会通过 !info 触发该命令
label: # 命令显示名称,用于显示在 LangBot 的 UI 上,支持多语言
en_US: Info
zh_Hans: Info
description: # 命令描述,用于显示在 LangBot 的 UI 上,支持多语言。可选。
en_US: 'Show information of the query'
zh_Hans: '发送此次消息的详细信息'
spec:
execution:
python:
path: info.py # 命令处理程序,请勿修改
attr: Info # 命令处理程序的类名,与 info.py 中的类名一致
```
## 插件处理
默认会生成如下代码(components/command/<命令名称>.py),您需要在`Info`类的`initialize`方法中注册并实现子命令的处理逻辑。
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from typing import Any, AsyncGenerator
from langbot_plugin.api.definition.components.command.command import Command, Subcommand
from langbot_plugin.api.entities.builtin.command.context import ExecuteContext, CommandReturn
class Info(Command):
async def initialize(self):
await super().initialize()
"Fill with your code here"
```
添加子命令:
```python
...
class Info(Command):
async def initialize(self):
await super().initialize()
@self.subcommand(
name="", # 空字符串表示根命令
help="Show information of the query", # 命令帮助信息
usage="info", # 命令使用示例,显示在命令帮助信息中
aliases=["i"], # 命令别名
)
async def send(self, context: ExecuteContext) -> AsyncGenerator[CommandReturn, None]:
print(context)
reply_text = f"Query ID: {context.query_id}\n"
reply_text += f"command: {context.command}\n"
reply_text += f"command_text: {context.command_text}\n"
reply_text += f"params: {context.params}\n"
reply_text += f"crt_params: {context.crt_params}\n"
reply_text += f"privilege: {context.privilege}\n"
reply_text += f"session: {context.session.launcher_type.value}_{context.session.launcher_id}\n"
yield CommandReturn(
text=reply_text,
)
@self.subcommand(
name="field",
help="Show information of the field",
usage="info field",
aliases=["f"],
)
async def field(self, context: ExecuteContext) -> AsyncGenerator[CommandReturn, None]:
print(context)
field_name = context.crt_params[0]
field_value = getattr(context, field_name)
yield CommandReturn(
text=f"{field_name}: {field_value}",
)
```
该代码中,`send`函数通过装饰器`@self.subcommand`注册为子命令,并在其中打印了命令的上下文(ExecuteContext)信息并拼接成一条回复消息。
### 子命令注册
`name` 为子命令名称,留空表示处理主命令。不为空则匹配第二个参数为命令,例如:
- `!info` 匹配 name="" 的子命令
- `!info field` 匹配 name="field" 的子命令
- `!info field value` 匹配 name="field" 的子命令,`value` 为子命令参数
子命令函数中,可通过 `context` 变量读取到命令参数。
上述命令效果如图:
<img width="600" src="/assets/image/zh/plugin/dev/components/info_command_demo.png" />
### 命令上下文
```python
class ExecuteContext(pydantic.BaseModel):
"""单次命令执行上下文"""
query_id: int
"""请求ID"""
session: provider_session.Session
"""本次消息所属的会话对象"""
command_text: str
"""命令完整文本"""
command: str
"""命令名称"""
crt_command: str
"""当前命令
多级命令中crt_command为当前命令,command为根命令。
例如:!plugin on Webwlkr
处理到plugin时,command为plugin,crt_command为plugin
处理到on时,command为plugin,crt_command为on
"""
params: list[str]
"""命令参数
整个命令以空格分割后的参数列表
"""
crt_params: list[str]
"""当前命令参数
多级命令中crt_params为当前命令参数,params为根命令参数。
例如:!plugin on Webwlkr
处理到plugin时,params为['on', 'Webwlkr'],crt_params为['on', 'Webwlkr']
处理到on时,params为['on', 'Webwlkr'],crt_params为['Webwlkr']
"""
privilege: int
"""会话权限级别"""
...
```
### 命令返回值
命令返回值(CommandReturn)目前支持返回文本(text)和图片(image_url,图片链接)、错误(error)。
```python
yield CommandReturn(
text=reply_text,
)
yield CommandReturn(
image_url=image_url,
)
```
命令处理支持返回多条消息,故请使用`yield`语句返回消息。
具体的返回值请参考`CommandReturn`的定义:[langbot_plugin.api.entities.builtin.command.context.CommandReturn](https://github.com/langbot-app/langbot-plugin-sdk/tree/main/src/langbot_plugin/api/entities/builtin/command/context.py)
## 接下来做什么
您已经了解了命令注册和命令执行的基本信息,接下来可以:
- 查看[插件通用 API](/zh/plugin/dev/apis/common)
@@ -0,0 +1,101 @@
# 组件:事件监听器
LangBot 的流水线在运行期间会产生一些事件,供插件 Hook 并处理。每个插件中只允许添加一个事件监听器(components/event_listener/default.yaml),但在其中可以注册任意数量的事件。
## 添加事件监听器组件
请在插件目录执行命令`lbp comp EventListener`,创建事件监听器组件不需要填写任何配置。
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
```
现在即会在`components/event_listener/`目录下生成`default.yaml`和`default.py`文件,`.yaml`定义了事件监听器的基础信息,`.py`是该事件监听器的处理程序:
```bash
➜ HelloPlugin > tree
...
├── components
│   ├── __init__.py
│   └── event_listener
│   ├── __init__.py
│   ├── default.py
│   └── default.yaml
...
```
## 清单文件:事件监听器
由于每个插件仅允许添加一个事件监听器,故清单文件中不需要修改任何内容。
## 插件处理
默认会生成如下代码(components/event_listener/default.py),您需要在`DefaultEventListener`类的`initialize`方法中注册并实现事件的处理逻辑。
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from langbot_plugin.api.definition.components.common.event_listener import EventListener
from langbot_plugin.api.entities import events, context
class DefaultEventListener(EventListener):
async def initialize(self):
await super().initialize()
"Fill with your code here"
```
添加对特定事件的监听:
```python
...
class DefaultEventListener(EventListener):
async def initialize(self):
await super().initialize()
@self.handler(events.PersonMessageReceived)
async def handler(event_context: context.EventContext):
print("Hello LangBot Plugin!")
print(event_context)
await event_context.reply(
platform_message.MessageChain([
platform_message.Plain(text=f"Hello from Nahida Plugin!"),
])
)
```
此代码将注册对`PersonMessageReceived(收到来自私聊的任何消息)`事件的监听,并在事件触发时打印事件的上下文(EventContext)信息,并调用事件上下文 API 回复一条消息。
EventContext 保存了该此事件触发的通用上下文信息,而 EventContext.event 为 PersonMessageReceived (或其他对应的事件类型)的对象,保存了该事件的详细信息。
```python
# EventContext 的定义摘要
class EventContext(pydantic.BaseModel):
"""事件上下文, 保存此次事件运行的信息"""
...
event: pydantic.SerializeAsAny[BaseEventModel]
"""此次事件的对象,具体类型为handler注册时指定监听的类型,可查看events.py中的定义"""
...
```
## 事件注册
事件注册通过装饰器`@self.handler`实现,装饰器参数为事件类型。可监听的事件均在`langbot_plugin.api.entities.builtin.events`中定义。
## 接下来做什么
您已经了解了事件监听器的基本信息,接下来可以:
- 在文档[流水线事件](/zh/plugin/dev/apis/pipeline-events)中查看`可监听的事件列表`和`事件上下文 API`
- 查看[插件通用 API](/zh/plugin/dev/apis/common)
+172
View File
@@ -0,0 +1,172 @@
# 组件:工具
LangBot 内置的 Local Agent 在执行期间会根据场景调用工具以与外界交互,目前支持通过插件和 MCP 两种方式添加工具。
## 添加工具组件
单个插件中能添加任意数量的工具,请在插件目录执行命令`lbp comp Tool`,并根据提示输入工具的配置。
```bash
➜ HelloPlugin > lbp comp Tool
Generating component Tool...
Tool name: get_weather_alerts
Tool description: Get weather alerts for a US state.
Component Tool generated successfully.
组件 Tool 生成成功。
```
现在即会在`components/tools/`目录下生成`get_weather_alerts.yaml`和`get_weather_alerts.py`文件,`.yaml`定义了工具的基础信息,`.py`是该工具的处理程序:
```bash
➜ HelloPlugin > tree
...
├── components
│   ├── __init__.py
│   └── tools
│   ├── __init__.py
│   ├── get_weather_alerts.py
│   └── get_weather_alerts.yaml
...
```
## 清单文件:工具
```yaml
apiVersion: v1 # 请勿修改
kind: Tool # 请勿修改
metadata:
name: get_weather_alerts # 工具名称,供 LLM 识别
label:
en_US: GetWeatherAlerts # 工具显示名称,用于显示在 LangBot 的 UI 上,支持多语言
zh_Hans: GetWeatherAlerts
description:
en_US: 'Get weather alerts for a US state.' # 工具描述,用于显示在 LangBot 的 UI 上,支持多语言。可选
zh_Hans: '获取美国某个州的天气预警'
spec:
parameters: [] # 工具参数,由 LLM 根据对话上下文生成值
llm_prompt: 'Get weather alerts for a US state.' # 工具提示词,供 LLM 判断是否调用该工具
execution:
python:
path: get_weather_alerts.py # 工具处理程序,请勿修改
attr: GetWeatherAlerts # 工具处理程序的类名,与 get_weather_alerts.py 中的类名一致
```
## 插件处理
默认会生成如下代码(components/tools/<工具名称>.py),您需要在`GetWeatherAlerts`类的`call`方法中实现此工具的调用和返回逻辑。
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from typing import Any
from langbot_plugin.api.definition.components.tool.tool import Tool
class GetWeatherAlerts(Tool):
async def call(self, params: dict[str, Any]) -> dict[str, Any]:
"""Fill your tool code here"""
return {}
```
在 `call` 方法中,实现该工具:
:::info
天气获取工具用例来自 MCP 的 [Server 编写示例](https://modelcontextprotocol.io/quickstart/server)。
:::
```python
from __future__ import annotations
from typing import Any
from langbot_plugin.api.definition.components.tool.tool import Tool
import httpx
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json"
}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def format_alert(feature: dict) -> str:
"""Format an alert feature into a readable string."""
props = feature["properties"]
return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""
class GetWeatherAlerts(Tool):
async def call(self, params: dict[str, Any]) -> dict[str, Any]:
"""Fill your tool code here"""
state = params.get("state", "CA")
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
```
在这个工具中,我们要从`params`中获取`state`参数,并调用 NWS API 获取该州的天气预警,故我们需要在清单文件的`parameters`中定义`state`参数。
`parameters` 的格式遵循 [JSON Schema](https://json-schema.org/docs),受 [OpenAI Function Calling 特性](https://platform.openai.com/docs/guides/function-calling#defining-functions)支持,且其根类型固定为`type: object`,请在`properties`中添加参数,并在`required`中添加必填参数说明。
```yaml
...
spec:
parameters:
type: object # 请勿修改
properties: # 请勿修改,在此处添加参数
state:
type: string
description: 'Two-letter US state code (e.g. CA, NY)'
required: # 在此添加必填参数说明
- state
...
```
本工具中还使用了`httpx`库,我们需要在插件目录的`requirements.txt`中添加`httpx`依赖。
```txt
# HelloPlugin/requirements.txt
langbot-plugin # 默认已存在
httpx
```
现在在插件目录执行命令`lbp run`,启动调试。在 LangBot 中配置支持工具调用的模型,并在对应流水线上选择使用该模型,即可使用该工具。
<img width="600" src="/assets/image/zh/plugin/dev/components/tool_use_in_chat.png" />
## 接下来做什么
您已经了解了工具的基本用法,接下来可以:
- 查看[插件通用 API](/zh/plugin/dev/apis/common)
-108
View File
@@ -1,108 +0,0 @@
# 组件扩展
::: warning
2025年3月~4月:我们正在进行 4.0 版本重构,组件定义变化较大,现阶段不建议使用组件扩展。请优先考虑使用基于事件的插件接口。
:::
> 请先阅读[插件介绍页](/zh/plugin/plugin-intro)
:::info 目录
[[toc]]
:::
普通的以 `BasePlugin` 子类包装的插件只能监听预定事件,而组件扩展则可以替换整个组件,实现更强大的功能扩展。
- 组件扩展不需要包装在 `BasePlugin` 子类中,可以放在插件目录下任意一个模块中
- 扩展的组件不受插件开关控制
- 扩展的组件通常由配置文件中的选项选用
## 可扩展的组件
![组件扩展图](/assets/image/zh/plugin/dev/develop_comp_arch_1.png)
扩展一个组件的方法,新建一个类,继承于组件的基类,然后在类上使用对应的装饰器,再根据使用方式来配置使用该实现的组件。
大部分组件中包含了`ap: pkg.core.app.Application`对象,可以用这个对象来与整个程序的各个部分进行交互。
大部分组件支持实现`async def initialize(self)`方法,用于在组件加载时进行异步初始化。
`ap`对象中包含了各个配置文件的管理器对象,可以用于访问各个配置文件。
### 命令
新增一个命令。
- 基类:`pkg.command.operator.CommandOperator`
- 装饰器:`pkg.command.operator.operator_class(name: str, help: str="", usage: str=None, alias: list[str]=[], privilege: int=1, parent_class: typing.Type[CommandOperator] = None)`
- 参考实现:`pkg.command.operators/`
- 使用方式:加载成功后,执行命令`!cmd`可查看所有命令和命令帮助信息。
### 内容过滤器
新增一个在处理消息前后判断是否要继续的内容处理器。
- 基类:`pkg.pipeline.cntfilter.filter.ContentFilter`
- 装饰器:`pkg.pipeline.cntfilter.filter.filter_class(name: str)`
- 参考实现:`pkg.pipeline.cntfilter.filters/`
- 使用方式:加载成功后,每次都会在过滤器实现声明的检查阶段调用内容过滤器的`process`方法。
### 长消息处理策略
新增一个长消息处理策略。类似:文本转合并转发消息组件、文本转图片。
- 基类:`pkg.pipeline.longtext.strategy.LongTextStrategy`
- 装饰器:`pkg.pipeline.longtext.strategy.strategy_class(name: str)`
- 参考实现:`pkg.pipeline.longtext.strategies/`
- 使用方式:需要在 `data/config/platform.json` 的 `long-text-strategy` 字段中将 `strategy` 设置为新增的策略名称。
### 会话限速算法
新增一个会话限速算法。会话请求在进入和退出时均会调用限速算法实现类的方法,实现类可以控制等待或丢弃请求。
- 基类:`pkg.pipeline.ratelimit.algo.RateLimitAlgo`
- 装饰器:`pkg.pipeline.ratelimit.algo.algo_class(name: str)`
- 参考实现:`pkg.pipeline.ratelimit.algos/`
- 使用方式:需要在 `data/config/pipeline.json` 的 `rate-limit` 字段中将 `algo` 设置为新增的算法名称。
### LLM 接口请求器
新增一个 LLM 接口请求器,例如:接入ChatGLM接口等。
- 基类:`pkg.provider.modelmgr.api.LLMAPIRequester`
- 装饰器:`pkg.provider.modelmgr.api.api_requester_class(name: str)`
- 参考实现:`pkg.provider.modelmgr.apis/`
- 使用方式:需要在`data/metadata/llm-models.json`中添加模型,将模型的`requester`字段设置为新增的请求器名称。具体请查看元数据配置板块。
### Prompt 加载器
新增一个 Prompt 加载器。
- 基类:`pkg.provider.sysprompt.loader.PromptLoader`
- 装饰器:`pkg.provider.sysprompt.loader.loader_class(name: str)`
- 参考实现:`pkg.provider.sysprompt.loaders/`
- 使用方式:需要在`data/config/provider.json`将`prompt-mode`设置为新增的加载器名称。
### 消息平台适配器
新增一个消息平台适配器,例如:接入 WhatsApp、Discord 等。
- 基类:`pkg.platform.adapter.MessageSourceAdapter`
- 装饰器:`pkg.platform.adapter.adapter_class(name: str)`
- 参考实现:`pkg.platform.adapters/`
- 使用方式:`data/config/platform.json`的`platform-adapters`中各个配置信息,将在初始化时自动根据`adapter`名称查找对应的适配器实现,并将配置传递给适配器以进行初始化。消息平台适配器实现复杂,建议多参考现有的实现。
### 对话历史消息截断器
新增一个对话历史消息截断器,在发送到模型之前对历史消息进行截断。
- 基类:`pkg.pipeline.msgtrun.truncator.Truncator`
- 装饰器:`pkg.pipeline.msgtrun.truncator.truncator_class(name: str)`
- 参考实现:`pkg.pipeline.msgtrun.truncators/`
- 使用方式:需要在`data/config/pipeline.json`的`msg-truncate`字段中设置需要使用的截断器`method`为新增的截断器名称。
### Chat 请求运行器
新增一个聊天消息运行器,默认的是 local-agent,提供了基本的 Prompt 组合、Agent 编排、多轮请求等功能。如果需要接入知识库或者本地 RAG,请在这里实现。
- 基类:`pkg.provider.runner.RequesterRunner`
- 装饰器:`pkg.provider.runner.runner_class(name: str)`
- 参考实现:`pkg.provider.runners/`
- 使用方式:需要在`data/config/provider.json`的`runner`字段中设置需要使用的运行器`runner`为新增的运行器名称。
+27
View File
@@ -0,0 +1,27 @@
# 迁移指南
本教程高亮了某些注意事项,请各位插件开发者在已阅读现版本插件教程后,再以此文档作为参考进行插件迁移。
## 从 3.x 支持的异步插件迁移
:::info
插件特征:
- 主要时间段:2024.2 ~ 2025.8
- 注册方式:`main.py` 中的插件类中使用 `@handler` 装饰器注册事件监听器、使用 `@llm_func` 装饰器注册内容函数(工具);处理函数均为异步函数(async def)
:::
- 插件和组件注册方式:已由以往的单一插件类中的装饰器注册方式,改为了清单文件注册方式,参阅[清单文件](/zh/plugin/dev/basic-info);并将各个不同类型的组件独立出来,详细请见[添加组件](/zh/plugin/dev/components/add)。
- 消息链实体:现已重构,每个消息链元素的构造方法现在需要显式传入**具名参数**,参阅[消息平台实体](/zh/plugin/dev/apis/messages)。
- 事件监听器:提供向后兼容性,但不再于上下文中提供`query: Query`对象,详细请参阅[流水线事件](/zh/plugin/dev/apis/pipeline-events)。
- 命令组件:现已重构,请参阅[命令组件](/zh/plugin/dev/components/command)。
## 从 2.x 和 3.x 支持的同步插件迁移
:::info
插件特征:
- 主要时间段:2023.1 ~ 2024.2
- 注册方式:`main.py` 中的插件类中使用 `@on` 装饰器注册事件监听器、使用 `@func` 装饰器注册内容函数(工具);处理函数均为同步函数(def)
:::
- 同步改异步:插件运行时现已改为全异步(基于asyncio),请将插件所有涉及 IO 操作的代码改为异步写法。
- 详细内容请参阅上述 3.x 异步插件迁移指南。
+65
View File
@@ -0,0 +1,65 @@
# 发布插件
通过 CLI 将插件发布至 [LangBot 插件市场](https://space.langbot.app/market)。
## 在 LangBot Space 创建账号
LangBot Space 是 LangBot 提供中心化服务平台。请访问[LangBot Space](https://space.langbot.app),点击右上角使用 GitHub 登录并创建账号。
<img width="600" src="/assets/image/zh/plugin/dev/login_space.png" />
这将跳转到 GitHub 授权页面,请根据提示完成授权。
## 使用 CLI 登录 LangBot Space
在任意目录执行命令`lbp login`:
```bash
➜ HelloPlugin > lbp login
Starting LangBot CLI login process...
Generating device code...
==================================================
Please copy the user code and complete verification in your browser:
User Code: 6CD64D56
Verification URL: https://space.langbot.app/auth/device
Device code expires in: 120 seconds
==================================================
Waiting for verification...
```
请点击`Verification URL`跳转至 LangBot Space,输入`User Code`完成登录。
<img width="600" src="/assets/image/zh/plugin/dev/space_device_flow.png" />
```bash
==================================================
✅ Login successful!
Access token saved to: ~/.langbot/cli/config.json
Token type: Bearer
Expires in: 21600 seconds
==================================================
```
登录成功后,您可以继续使用 CLI 上传插件。
## 上传插件
请在您的插件目录下执行`lbp publish`:
```bash
➜ HelloPlugin > lbp publish
Building plugin to dist/tmp...
- Skipping ignored directory: dist
- Skipping ignored directory: __pycache__
...
Plugin built successfully: dist/tmp/RockChinQ-HelloPlugin-0.1.0.lbpkg
✅ Plugin published successfully. You can check it on https://https://space.langbot.app/market
```
您现在可以在[LangBot 插件市场](https://space.langbot.app/market)查看您的插件提交任务。请等待审核通过后,您可以在插件市场查看您的插件。
<img width="600" src="/assets/image/zh/plugin/dev/plugin_submission.png" />
有任何问题,您可以发送邮件到`[email protected]`或通过[社区](/zh/insight/community)联系我们。
+133 -312
View File
@@ -1,312 +1,133 @@
# 插件开发教程
:::warning
4.0 插件机制正在重构,请先查看 [3.x 插件开发教程](https://v3.docs.langbot.app/plugin/dev/tutor.html)
:::
> 请先阅读[插件介绍页](/zh/plugin/plugin-intro)
> 建议先阅读本项目源码,了解项目架构
> 问题、需求请到仓库issue发起
> **提问前请先靠自己尝试**
:::info 目录
[[toc]]
:::
## 💬 简介
本页讲解普通插件开发步骤,插件内部有两种方法:事件监听器、内容函数。
事件监听器能在某些事件触发时进行响应,更改行为。内容函数可供大语言模型调用,具体请查看插件介绍页。
## 💻 快速开始
使用[HelloPlugin](https://github.com/langbot-app/HelloPlugin)作为模板生成插件代码仓库,然后将仓库代码克隆到`plugins`目录下。
修改插件的 README.md 文件中需要修改的部分。
编辑`main.py`输入以下内容:
```Python
from pkg.plugin.context import register, handler, llm_func, BasePlugin, APIHost, EventContext
from pkg.plugin.events import * # 导入事件类
"""
在收到私聊或群聊消息"hello"时,回复"hello, <发送者id>!"或"hello, everyone!"
"""
class HelloPlugin(BasePlugin):
# 插件加载时触发
def __init__(self, host: APIHost):
pass
# 异步初始化
async def initialize(self):
pass
# 当收到个人消息时触发
@handler(PersonNormalMessageReceived)
async def person_normal_message_received(self, ctx: EventContext):
msg = ctx.event.text_message # 这里的 event 即为 PersonNormalMessageReceived 的对象
if msg == "hello": # 如果消息为hello
# 输出调试信息
self.ap.logger.debug("hello, {}".format(ctx.event.sender_id))
# 回复消息 "hello, <发送者id>!"
ctx.add_return("reply", ["hello, {}!".format(ctx.event.sender_id)])
# 阻止该事件默认行为(向接口获取回复)
ctx.prevent_default()
# 当收到群消息时触发
@handler(GroupNormalMessageReceived)
async def group_normal_message_received(self, ctx: EventContext):
msg = ctx.event.text_message # 这里的 event 即为 GroupNormalMessageReceived 的对象
if msg == "hello": # 如果消息为hello
# 输出调试信息
self.ap.logger.debug("hello, {}".format(ctx.event.sender_id))
# 回复消息 "hello, everyone!"
ctx.add_return("reply", ["hello, everyone!"])
# 阻止该事件默认行为(向接口获取回复)
ctx.prevent_default()
# 插件卸载时触发
def __del__(self):
pass
```
:::info
解读此插件程序
- `import pkg.plugin.context`导入`register(用于注册插件类)`, `handler(用于注册事件监听器)`, `llm_func(用于注册内容函数)`,`BasePlugin(插件基类)`, `APIHost(API宿主)`, `EventContext(事件上下文类)`等内容
- `import pkg.plugin.events`导入所有支持的事件类
- 声明类`HelloPlugin`继承于`BasePlugin`,此类可以随意命名,插件名称只与`register`调用时的参数有关
- 声明此类的`__init__`方法,此方法是可选的,其中的代码将在主程序启动时加载插件的时候被执行
- 插件类中还支持添加一个异步方法`async def initialize(self)`用于异步初始化
- `@handler`将方法`person_normal_message_received`标记为一个事件监听器,处理`PersonNormalMessageReceived`(收到私聊消息并在获取OpenAI回复前触发)事件,此方法可以随意命名,绑定的事件只与`handler`中的参数有关,更多支持的事件可到`pkg.plugin.events`文件中查看或查看下方`API`节
- 通过`self.ap.logger`日志器输出调试信息,插件类父类中的`ap`对象是整个程序的上下文对象,可以通过这个对象访问程序内所有对象
- 事件监听器方法获得的第二个参数`ctx`即为本次事件的上下文,其中的`event`为本次事件的事件对象,可以从中取出相关参数,具体事件可以取得的参数请查看`pkg.plugin.events`文件中的每个事件类的注释
- 事件监听器方法内部从参数中取出`text_message`参数,判断是否为`hello`,如果是就将返回值`reply`设置为`["hello, {}!".format(ctx.event.sender_id)]`,接下来调用`ctx.prevent_default()`方法,阻止原程序默认行为
- 每个事件支持的返回值请查看`pkg.plugin.events`中的每个事件的注释
- 用相似的程序注册`GroupNormalMessageReceived`事件处理群消息
编写完毕保存后,重新启动主程序,前往 WebUI 插件页即可看到插件
:::
此插件将实现:私聊收到`hello`消息时回复`hello, <发送者QQ号>!`,群聊收到`hello`消息时回复`hello, everyone!`
### 编写清单文件
从 LangBot 4.0 开始,将使用清单文件来注册插件。
请在 main.py 同目录下新建文件 `manifest.yaml`。如果已经存在了,请按照下方格式修改。
```yaml
apiVersion: v1 # 不要改动
kind: Plugin # 不要改动
metadata:
# author 和 name 唯一确定一个插件
author: langbot # 插件作者,修改为你的名称
name: Hello # 插件名称,修改为你的插件名称
repository: 'https://github.com/langbot-app/HelloPlugin' # 插件仓库地址,修改为你的插件 GitHub 仓库地址
version: 0.1.0 # 插件版本,修改为你的插件版本
description: # 插件简介,修改为你的插件简介,支持多语言。语言代码采用 RFC 4646 标准。
en_US: Plugin for sending hello
zh_Hans: 示例插件
label: # 插件显示名称,支持多语言。在 WebUI 上会显示对应语言的 label。语言代码采用 RFC 4646 标准。
en_US: Hello
zh_Hans: Hello
spec:
# 插件配置(可选),可配置多项
config:
- name: github_token # 配置项名称
label: # 配置项显示名称,支持多语言。语言代码采用 RFC 4646 标准。
en_US: Github Token
zh_Hans: Github Token
description: # 配置项描述,支持多语言。可选。
en_US: Image downloading requires a Github token
zh_Hans: 如果不填的话,图片可能会下载失败
type: string # 配置项类型,支持 string, integer, float, boolean 等
default: '' # 配置项默认值
required: false # 配置项是否必填
execution:
python:
path: main.py # 插件主程序路径,必须与上方插件入口代码的文件名相同
attr: HelloPlugin # 插件类名,必须与上方代码中声明的类名相同
```
:::info
配置项支持类型:
- string: 字符串
- integer: 整数
- float: 浮点数
- boolean: 布尔值
- array[string]: 字符串数组
- select: 下拉框,可从多个选项中选择一个
- 需要配置 options 选项,表示下拉框的选项
```yaml
- name: mode
label:
en_US: Mode
zh_Hans: 模式
type: select
options:
- name: mode1
label:
en_US: Mode 1
zh_Hans: 模式 1
- name: mode2
label:
en_US: Mode 2
zh_Hans: 模式 2
```
- prompt-editor: 提示词编辑器。
- llm-model-selector: LLM模型选择器。
用户通过 WebUI 修改配置项后,插件可以在 self.config 中获取到配置项的值。
:::
## ❗ 规范(重要)
- 请每个插件独立一个目录以便管理,建议在Github上创建一个仓库储存单个插件,以便获取和更新
- 命名规范(重要):
- 插件作者:使用英文,大小写均可,如`RockChinQ`
- 插件名:使用`大驼峰命名法`,如`Hello`、`ExamplePlugin`、`ChineseCommands`等
- 插件仓库:请使用大驼峰命名法,建议直接与插件名相同,如`HelloPlugin`
- 插件简介:建议携带`LangBot 插件`字样
- 一个目录内可以存放多个Python程序文件,以独立出插件的各个功能,便于开发者管理,但不建议在一个目录内注册多个插件
- 插件需要的依赖库请在插件目录下的`requirements.txt`中指定,程序从储存库获取此插件时将自动安装依赖
## 🪝 内容函数
通过[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling)实现的`内容函数`,这是一种嵌入对话中,由GPT自动调用的函数。
> 您的插件不一定必须包含内容函数,请先查看内容函数页了解此功能
### 内容函数编写步骤
1️⃣ 请先按照上方步骤编写您的插件基础结构,现在请删除(当然你也可以不删,只是为了简洁)上述插件内容的诸个由`@handler`装饰的类函数
<details>
<summary>删除后的类结构</summary>
```python
class HelloPlugin(Plugin):
# 插件加载时触发
def __init__(self, plugin_host: APIHost):
pass
# 插件卸载时触发
def __del__(self):
pass
```
</details>
2️⃣ 现在我们将以下函数添加到刚刚删除的函数的位置(作为类方法)
```Python
# 要添加的函数
@llm_func(name="access_the_web") # 设置函数名称
async def access_web(self, query, url: str):
"""Call this function to search about the question before you answer any questions.
- Do not search through baidu.com at any time.
- If you need to search somthing, visit https://www.google.com/search?q=xxx.
- If user ask you to open a url (start with http:// or https://), visit it directly.
- Summary the plain content result by yourself, DO NOT directly output anything in the result you got.
Args:
url(str): url to visit
Returns:
str: plain text content of the web page
"""
import requests
from bs4 import BeautifulSoup
# 你需要先使用
# pip install beautifulsoup4
# 安装依赖
r = requests.get(
url,
timeout=10,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183"
}
)
soup = BeautifulSoup(r.text, 'html.parser')
s = soup.get_text()
# 删除多余的空行或仅有\t和空格的行
s = re.sub(r'\n\s*\n', '\n', s)
if len(s) >= 512: # 截取获取到的网页纯文本内容的前512个字
return s[:512]
return s
```
#### 请注意:
- 函数的注释必须严格按照要求的格式进行书写,具体格式请查看[此文档](https://github.com/RockChinQ/CallingGPT/wiki/1.-Function-Format#function-format)
- 内容函数和`以 @handler 装饰的事件监听器`可以同时存在于同一个插件,并同时受到插件开关的控制
- 务必确保您使用的模型支持函数调用功能
3️⃣ 现在您的程序已具备网络访问功能,重启程序,询问机器人有关在线的内容或直接发送文章链接请求其总结。
- 这仅仅是一个示例,需要更高效的网络访问能力支持插件,请查看[WebwlkrPlugin](https://github.com/RockChinQ/WebwlkrPlugin)
## 📦 上架插件市场
当你的插件开发完成后,请上传到 GitHub,并在 [LangBot 仓库提出 issue](https://github.com/langbot-app/LangBot/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6) ,待审核后将会添加到插件市场,约半个小时后会在 LangBot WebUI 插件页的插件市场上可见。
![](/assets/image/zh/plugin/dev/plugin_dev_tutor_01.png)
## 📄 名词解释
### 说明
事件监听器方法获得的`ctx: EventContext`对象可以到`pkg.plugin.context`模块中查看其结构,`ctx`对象的`event`属性为本次事件的事件对象,可以从中取出相关参数,具体事件可以取得的参数请查看`pkg.plugin.events`文件中的每个事件类的注释。
事件返回值均为**可选**的,可以通过调用`ctx.add_return(key: str, ret)`来提交返回值
### 事件
若一个事件是一次请求(用户发送消息)中的事件,其事件对象中会含有`query`对象,这个对象中包含了此次请求(即用户发送一条消息的处理过程,一个请求过程可能会触发多个事件)处理过程中的上下文数据。
所有支持的事件,请查看`pkg.plugin.events`文件中的每个事件类的注释。
关于`消息链组件`,请查看 [消息平台实体](./messages)
### BasePlugin 结构
#### self.ap
在插件的方法中访问`self.ap`即为`pkg.core.app.Application`类的对象,包含了整个程序的上下文对象,可以通过这个对象访问程序内所有对象。
#### self.host
在插件的方法中访问`self.host`即为`pkg.plugin.context.APIHost`类的对象,提供了主程序的一些API,详细请查看其源码。
### API 参考
关于插件可以调用的 API,请查看 [API 参考](./api-ref)
### 插件开发交流群
插件开发、发布、交流
- LangBot 插件开发:1032327138 ([链接](https://qm.qq.com/q/G7ENGTjeou))
# 插件开发教程
## 架构基础
在 4.0 版本中,我们引入了一种高安全性、高灵活性的生产级插件系统,并为插件开发者提供了丰富的 API 和易用的配套工具。
<img width="600" src="/assets/image/zh/plugin/dev/plugin_system_arch.png" />
Plugin Runtime (插件运行时),用于管理插件的生命周期,协调 LangBot 与插件之间的交互。有两种运行模式:`stdio` 和 `websocket`。当 LangBot 是由用户直接启动(未运行在容器内时),会使用 `stdio` 模式,这种场景多为个人用户或轻量级环境。当 LangBot 运行在容器内时,会使用 `websocket` 模式,此场景专为生产级环境设计。
Plugin Runtime 会自动启动各个已安装的插件,通过 stdio 交互。在插件开发场景中,开发者可以使用 `lbp` 命令行工具启动插件并通过 WebSocket 连接到已启动的 Runtime 进行调试。
### 插件结构
一个插件的目录结构与以下类似:
```bash
➜ HelloPlugin > tree
.
├── assets
│ └── icon.svg # 插件图标,会被展示在插件市场页面
├── components # 插件组件目录,存放插件的各个组件代码和清单文件
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ ├── info.py
│ │ └── info.yaml
│ ├── event_listener
│ │ ├── __init__.py
│ │ ├── default.py
│ │ └── default.yaml
│ └── tools
│ ├── __init__.py
│ ├── get_weather_alerts.py
│ └── get_weather_alerts.yaml
├── main.py # 插件主程序,监听插件的生命周期
├── manifest.yaml # 插件清单文件,描述插件的元信息
├── README.md # 插件说明文件,描述插件的功能和使用方法,会被展示在插件市场页面
└── requirements.txt # 插件依赖文件
```
main.py 中的插件类是每个插件通用的代码,在插件被启动时会被初始化,并传入上下文信息(配置信息等)。您可在此实现插件的初始化和关闭时的逻辑。插件类会继承于`langbot_plugin.api.definition.plugin.BasePlugin`,并提供 [LangBot 全局 API](/zh/plugin/dev/apis/common)。
各个组件是您插件的核心功能模块,可以根据需要[添加和删除组件](/zh/plugin/dev/components/add)。不同的组件会在不同的情况下被调用,这种设计方便后续扩展插件功能。各个组件均直接继承于`langbot_plugin.api.definition.components`包下的各个组件基类,并在其中包含`plugin: BasePlugin`对象,您可直接调用插件的 API。
::: info
- 详细的 API 说明请参考[API 参考](/zh/plugin/dev/apis/common)。
请您继续阅读本页教程,了解插件的开发流程。
:::
## 安装 CLI
请确保您已安装 Python 3.10 或更高版本,并已安装 [uv 包管理器](https://docs.astral.sh/uv/)。
在任意空目录执行命令,安装 LangBot CLI 和 SDK
```bash
pip install langbot_plugin==0.1.1b5
```
## 初始化插件目录
假设您的插件名称为 `HelloPlugin`,则在任意目录下创建目录`HelloPlugin`,并进入该目录,执行命令初始化插件:
```bash
lbp init
```
按照提示输入`Author(作者)`、`Description(描述)`等信息
:::info
您也可以使用`lbp init HelloPlugin`命令,在子目录`HelloPlugin`中初始化插件。
<img width="600" src="/assets/image/zh/plugin/dev/create_plugin.png" />
:::
此操作将生成插件的初始文件。您现在可以在您喜爱的编辑器中打开 `HelloPlugin` 目录,开始编写插件代码。
:::info
若提示找不到 `lbp` 命令,可能是由于您未正确设置 `PATH` 环境变量。
可使用`python -m langbot_plugin.cli.__init__`替代`lbp`命令。
例如:
```bash
python -m langbot_plugin.cli.__init__ init HelloPlugin
cd HelloPlugin
```
:::
## 启动调试
您需要先部署并启动 LangBot,确保 Plugin Runtime 正在运行并监听 `5401` 端口。
::: info
Runtime 有两种启动方式:
- `Stdio` 模式:当您使用源代码启动 LangBot 并未携带启动参数`--standalone-runtime`时,LangBot 会自动以子进程的形式启动 Plugin Runtime,并通过 `stdio`(标准输入输出流)模式与 Plugin Runtime 通信。此时 Runtime 会加载 LangBot 根目录的`data/plugins`目录下的插件。并监听 LangBot 运行主机的`5401`端口作为调试端口。
- `WebSocket` 模式:
- 生产环境:当您使用官方提供的`docker-compose.yaml`启动 LangBot 时,会在独立的容器运行 Plugin Runtime,LangBot 也会因为携带启动参数`--standalone-runtime`而以 WebSocket 模式与 Plugin Runtime 通信。
- 开发环境:若您通过源代码启动 LangBot,并携带启动参数`--standalone-runtime`,LangBot 按照`data/config.yaml`中配置的`plugin.runtime_ws_url`地址(端口通常是5400)连接到已启动的 Plugin Runtime。您需要自行启动独立运行的 Plugin Runtime,见[开发 Plugin Runtime](/zh/develop/plugin-runtime)。
无论是`stdio`或`websocket`模式,Plugin Runtime 都会监听其所在的主机的`5401`端口作为调试端口供插件调试时连接。
当您在开发插件时,推荐您按照[开发配置](/zh/develop/dev-config)中的方式 LangBot,此方式将以 Stdio 形式启动 Plugin Runtime,便于插件开发。
:::
复制插件目录下的`.env.example`文件为`.env`,检查或修改`DEBUG_RUNTIME_WS_URL`为您的 Plugin Runtime 的 WebSocket 地址。
```bash
cp .env.example .env
```
启动插件调试,您将看到插件的输出:
```bash
lbp run
```
<img width="600" src="/assets/image/zh/plugin/dev/launch_debug_plugin.png" />
并可以在 LangBot 的 WebUI 中看到此插件已被加载。
<img width="600" src="/assets/image/zh/plugin/dev/debug_plugin_loaded.png" />
## 接下来做什么
该教程将指引您逐步完善插件功能。
- 修改插件信息:插件已创建,并填入了最基础的插件信息,请您[完善插件信息](/zh/plugin/dev/basic-info)。
- 添加组件:插件组件是插件的核心功能单元,您可以根据需求[添加组件](/zh/plugin/dev/components/add)。