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
+48 -4
View File
@@ -175,10 +175,31 @@ export default defineConfig({
collapsed: true,
items: [
{ text: "基础教程", link: "/zh/plugin/dev/tutor" },
{ text: "消息平台实体", link: "/zh/plugin/dev/messages" },
{ text: "API 参考", link: "/zh/plugin/dev/api-ref" },
{ text: "完善插件信息", link: "/zh/plugin/dev/basic-info" },
{
text: "组件开发",
collapsed: true,
items: [
{ text: "添加组件", link: "/zh/plugin/dev/components/add" },
{ text: "组件:事件监听器", link: "/zh/plugin/dev/components/event-listener" },
{ text: "组件:命令", link: "/zh/plugin/dev/components/command" },
{ text: "组件:工具", link: "/zh/plugin/dev/components/tool" },
],
},
{
text: "API 参考",
collapsed: true,
items: [
{ text: "插件通用 API", link: "/zh/plugin/dev/apis/common" },
{ text: "流水线事件", link: "/zh/plugin/dev/apis/pipeline-events" },
{ text: "消息平台实体", link: "/zh/plugin/dev/apis/messages" },
],
},
{ text: "迁移指南", link: "/zh/plugin/dev/migration" },
{ text: "发布插件", link: "/zh/plugin/dev/publish" },
],
},
{ text: "系统兼容性", link: "/zh/plugin/compatibility" },
],
},
{
@@ -215,6 +236,7 @@ export default defineConfig({
items: [
{ text: "开发配置", link: "/zh/develop/dev-config" },
{ text: "组件架构", link: "/zh/develop/comp-arch" },
{ text: "插件运行时", link: "/zh/develop/plugin-runtime" },
{
text: "适配器开发",
collapsed: true,
@@ -397,10 +419,31 @@ export default defineConfig({
collapsed: true,
items: [
{ text: "Basic Tutorial", link: "/en/plugin/dev/tutor" },
{ text: "Message Platform Entities", link: "/en/plugin/dev/messages" },
{ text: "API Reference", link: "/en/plugin/dev/api-ref" },
{ text: "Complete Plugin Information", link: "/en/plugin/dev/basic-info" },
{
text: "Component Development",
collapsed: true,
items: [
{ text: "Adding Components", link: "/en/plugin/dev/components/add" },
{ text: "Component: Event Listener", link: "/en/plugin/dev/components/event-listener" },
{ text: "Component: Command", link: "/en/plugin/dev/components/command" },
{ text: "Component: Tool", link: "/en/plugin/dev/components/tool" },
],
},
{
text: "API Reference",
collapsed: true,
items: [
{ text: "Plugin Common APIs", link: "/en/plugin/dev/apis/common" },
{ text: "Pipeline Events", link: "/en/plugin/dev/apis/pipeline-events" },
{ text: "Message Platform Entities", link: "/en/plugin/dev/apis/messages" },
],
},
{ text: "Migration Guide", link: "/en/plugin/dev/migration" },
{ text: "Publish Plugin", link: "/en/plugin/dev/publish" },
],
},
{ text: "System Compatibility", link: "/en/plugin/compatibility" },
],
},
{
@@ -433,6 +476,7 @@ export default defineConfig({
items: [
{ text: "Development Configuration", link: "/en/develop/dev-config" },
{ text: "Component Architecture", link: "/en/develop/comp-arch" },
{ text: "Plugin Runtime", link: "/en/develop/plugin-runtime" },
],
},
],
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

+1 -1
View File
@@ -15,7 +15,7 @@ Clone the project:
```bash
git clone https://github.com/langbot-app/LangBot
cd LangBot
cd LangBot/docker
```
Start the container:
+1
View File
@@ -3,6 +3,7 @@
:::warning
1. Please use Python 3.10.1 (not including 3.10.0) or higher version, 3.10.14 is recommended. You need to install Python if you don't have it.
2. This method is not recommended, manual deployment may not be supported in the future. Please consider using Docker deployment.
3. **Do not deploy LangBot natively on Windows, [this will make plugins unusable](/en/plugin/compatibility)**, please use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) or Docker deployment.
:::
## Installing the Main Program
+1 -1
View File
@@ -39,7 +39,7 @@ When using external LLMOps platforms, LangBot explicitly passes the following pa
- `conversation_id`: String, user session ID, generated by LangBot. It will be regenerated after the user resets the session
- `msg_create_time`: Numeric type, timestamp (seconds) when this message was received
You can [customize any variable through plugins](/en/plugin/dev/api-ref.html#%E8%AE%BE%E7%BD%AE%E8%AF%B7%E6%B1%82%E5%8F%98%E9%87%8F).
You can [customize any variable through plugins](/zh/plugin/dev/apis/common.html#set-request-variables).
![Dify Workflow Start Node Configuration](/assets/image/zh/workshop/dify-service-api/dify_workflow_var.png)
+15
View File
@@ -0,0 +1,15 @@
# System Compatibility
LangBot's plugin system utilizes mechanisms such as `Asyncio` and `Subprocess`, which work well on Unix-like systems (such as Linux, macOS), but have some issues on Windows, preventing plugins from running properly.
Python's asynchronous event loop on Windows defaults to using `ProactorEventLoop`, but this event loop lacks support for Stdio, while LangBot in non-Docker environments relies on Stdio to communicate with Plugin Runtime, and Plugin Runtime also depends on Stdio to communicate with plugins.
If we switch to using `SelectorEventLoop`, it would prevent Plugin Runtime and plugins from starting properly, as `SelectorEventLoop` does not support `Subprocess`.
For detailed information about these issues, please refer to: [Python Official Documentation](https://docs.python.org/3.13/library/asyncio-platforms.html)
## Proposed Solution
In the future, we plan to support WebSocket as the default communication method between LangBot and Plugin Runtime, as well as between Plugin Runtime and plugins on Windows.
For now, please deploy LangBot on Unix-like systems, or use Docker or WSL2 on Windows.
-164
View File
@@ -1,164 +0,0 @@
# API Reference
:::info Table of Contents
[[toc]]
:::
## Event API
The following APIs are only available in event handler functions.
### Reply to Message
```python
await ctx.reply(message_chain: MessageChain)
```
Reply to the conversation that initiated this event.
- `message_chain`: [Message Chain object](./messages.md), the program can automatically convert it to the target message platform's message chain
### Send Active Message
> Due to poor support for active messages in QQ Official API, if users are using QQ Official API, sending active messages may fail
```python
await ctx.send_message(target_type: str, target_id: str, message_chain: MessageChain)
```
Send an active message to the target.
- `target_type`: Target type, possible values: `"person"`, `"group"`
- `target_id`: Target ID (QQ number or group number)
- `message_chain`: [Message Chain object](./messages.md), the program can automatically convert it to the target message platform's message chain
### Prevent Default Event Behavior
```python
ctx.prevent_default()
```
Prevent the default behavior of this event, i.e., stop the subsequent behaviors in the processing flow (such as getting a reply from the interface when receiving a private chat message, getting a reply from the interface when receiving a group message, etc.).
### Prevent Subsequent Plugin Execution
```python
ctx.prevent_postorder()
```
Prevent the execution of subsequent plugins for this event. To modify the execution order of plugins, please change the priority through the methods described in the "Plugin Management" section of the "Plugin Introduction".
### Add Return Value
```python
ctx.add_return(name: str, value: Any)
```
Add a return value. Event return values are all **optional**. For return values supported by each event, please check the comments of each event in `pkg.plugin.events`.
## Request API
A `Request (Query)` refers to the program context when a user sends a question to LangBot and LangBot processes that question. The APIs in this section are used to interact with these contexts.
- In event handler functions, the Query object is usually located at `ctx.event.query`
### Set Request Variable
```python
ctx.event.query.set_variable(key: str, value: typing.Any)
```
Request variables are a `dict` bound to this Query, which contains some program context information. If you are using Dify or Alibaba Cloud Bailian or other LLMOps runners, [these variables will be passed to the corresponding platform's API as variables](/en/deploy/pipelines/readme.html#conversation-variables).
If you need to set variables using plugins, it is recommended to handle them during the `PromptPreProcessing` event.
- `key`: The name of the variable
- `value`: The value of the variable
### Get Request Variable
```python
ctx.event.query.get_variable(key: str)
```
Get a request variable.
- `key`: Variable name
- Return value: `typing.Any`
### Get All Request Variables
```python
ctx.event.query.get_variables()
```
Get all variable values set for this Query object.
- Return value: `dict[str, typing.Any]`
## LangBot API
The following APIs are provided directly by LangBot for plugins to call and can be called outside of event handler functions.
- `host` represents an object of the `pkg.plugin.context.APIHost` class, which is included in each plugin class.
### Get Enabled Message Platform Adapters List
```python
host.get_platform_adapters()
```
Get a list of enabled message platform adapters.
- Return value: `list[pkg.platform.adapter.MessageSourceAdapter]`
### Send Active Message
```python
await host.send_active_message(adapter: pkg.platform.adapter.MessageSourceAdapter, target_type: str, target_id: str, message_chain: MessageChain)
```
Send an active message to the target.
- `adapter`: Message platform adapter, obtained through `host.get_platform_adapters()` and selecting one of them
- `target_type`: Target type, possible values: `"person"`, `"group"`
- `target_id`: Target ID (QQ number or group number)
- `message_chain`: [Message Chain object](./messages.md), the program can automatically convert it to the target message platform's message chain
:::info
- Some message platforms may not support active messages or have strict limitations
- Some message platform adapters (such as `aiocqhttp`) act as servers waiting for message platforms to connect and push messages. Before a successful connection, sending active messages will fail
:::
Example:
```python
import asyncio
import pkg.platform.types as platform_types
...
# initialize function of a plugin
# This code will attempt to send a message "hello, world!" to QQ number 1010553892 10 seconds after LangBot starts
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 @@
# Plugin Common APIs
LangBot provides a series of APIs for plugins to operate various LangBot modules and control message contexts.
:::info Table of Contents
[[toc]]
:::
## Request API
Operations for the currently processing user request (message). Only available in `EventListener` and `Command` components. Access methods:
- In event handler methods of `EventListener`: Internal methods of `event_context: context.EventContext` object
- In subcommand handler methods of `Command`: Internal methods of `context: ExecuteContext` object
### Direct Reply Message
Directly reply with a message chain to the session where the current request is located.
For message chain construction methods, please refer to [Message Platform Entities](/en/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
"""
# Usage example
await event_context.reply(
platform_message.MessageChain([
platform_message.Plain(text="Hello, world!"),
]),
)
```
### Get Bot UUID
Get the UUID of the bot that originated the current request.
```python
async def get_bot_uuid(self) -> str:
"""Get the bot uuid"""
# Usage example
bot_uuid = await event_context.get_bot_uuid()
```
### Set Request Variables
Some information in a single request is stored in request variables. When using external LLMOps platforms like Dify, [these variables are explicitly passed to LLMOps platforms](/en/deploy/pipelines/readme.html#request-variables).
```python
async def set_query_var(self, key: str, value: Any):
"""Set a query variable"""
# Usage example
await event_context.set_query_var("key", "value")
```
### Get Request Variables
Get a single request variable.
```python
async def get_query_var(self, key: str) -> Any:
"""Get a query variable"""
# Usage example
query_var = await event_context.get_query_var("key")
```
### Get All Request Variables
```python
async def get_query_vars(self) -> dict[str, Any]:
"""Get all query variables"""
# Usage example
query_vars = await event_context.get_query_vars()
```
## LangBot API
These APIs can be called in any plugin component. Access methods:
- In the plugin root directory `main.py`: Internal methods of the `self` object, these APIs are all provided by the plugin class parent class `BasePlugin`.
- In any plugin component class: Internal methods of the `self.plugin` object.
### Get Plugin Configuration
Plugin configuration format can be written in `manifest.yaml`, and users need to fill it out according to the plugin configuration format in LangBot's plugin management. Plugin code can then call this API to get plugin configuration information.
```python
def get_config(self) -> dict[str, typing.Any]:
"""Get the config of the plugin."""
# Usage example
config = self.plugin.get_config()
```
### Get LangBot Version
Get the LangBot version number, returned as a string in format `v<major>.<minor>.<patch>`.
```python
async def get_langbot_version(self) -> str:
"""Get the langbot version"""
# Usage example
langbot_version = await self.plugin.get_langbot_version()
```
### Get Configured Bot List
Returns a list of all bot UUIDs.
```python
async def get_bots(self) -> list[str]:
"""Get all bots"""
# Usage example
bots = await self.plugin.get_bots()
```
### Get Bot Information
Get bot information.
```python
async def get_bot_info(self, bot_uuid: str) -> dict[str, Any]:
"""Get a bot info"""
# Usage example
bot_info = await self.plugin.get_bot_info("de639861-be05-4018-859b-c2e2d3e0d603")
# Return example
{
"uuid": "de639861-be05-4018-859b-c2e2d3e0d603",
"name": "aiocqhttp",
"description": "Migrated from 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": { # Present if the bot is currently running
"bot_account_id": 960164003 # Bot account ID
}
}
```
### Send Proactive Message
Send proactive messages through bot UUID and target session ID.
For message chain construction methods, please refer to [Message Platform Entities](/en/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"""
# Usage example
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!")]),
)
```
### Get Configured LLM Model List
Returns a list of UUIDs for all configured LLM models.
```python
async def get_llm_models(self) -> list[str]:
"""Get all LLM models"""
# Usage example
llm_models = await self.plugin.get_llm_models()
```
### Invoke LLM Model
Invoke an LLM model, returns an LLM message. Non-streaming.
```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"""
# Usage example
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={},
)
```
### Set Plugin Persistent Data
Persistently store plugin data. Data stored through this interface can only be accessed by this plugin. Values need to be converted to bytes manually.
```python
async def set_plugin_storage(self, key: str, value: bytes) -> None:
"""Set a plugin storage value"""
# Usage example
await self.plugin.set_plugin_storage("key", b"value")
```
### Get Plugin Persistent Data
```python
async def get_plugin_storage(self, key: str) -> bytes:
"""Get a plugin storage value"""
# Usage example
plugin_storage = await self.plugin.get_plugin_storage("key")
```
### Get All Plugin Persistent Data Keys
```python
async def get_plugin_storage_keys(self) -> list[str]:
"""Get all plugin storage keys"""
# Usage example
plugin_storage_keys = await self.plugin.get_plugin_storage_keys()
```
### Delete Plugin Persistent Data
```python
async def delete_plugin_storage(self, key: str) -> None:
"""Delete a plugin storage value"""
# Usage example
await self.plugin.delete_plugin_storage("key")
```
### Get Workspace Persistent Data
Data stored through this interface can be accessed by all plugins. Values need to be converted to bytes manually.
```python
async def set_workspace_storage(self, key: str, value: bytes) -> None:
"""Set a workspace storage value"""
# Usage example
await self.plugin.set_workspace_storage("key", b"value")
```
### Get Workspace Persistent Data
```python
async def get_workspace_storage(self, key: str) -> bytes:
"""Get a workspace storage value"""
# Usage example
workspace_storage = await self.plugin.get_workspace_storage("key")
```
### Get All Workspace Persistent Data Keys
```python
async def get_workspace_storage_keys(self) -> list[str]:
"""Get all workspace storage keys"""
# Usage example
workspace_storage_keys = await self.plugin.get_workspace_storage_keys()
```
### Delete Workspace Persistent Data
```python
async def delete_workspace_storage(self, key: str) -> None:
"""Delete a workspace storage value"""
# Usage example
await self.plugin.delete_workspace_storage("key")
```
+96
View File
@@ -0,0 +1,96 @@
# Message Platform Entities
:::info Table of Contents
[[toc]]
:::
LangBot supports multiple messaging platforms, and each platform has different `message entity` formats. To abstract away these differences, we have implemented a unified standard. Plugin developers only need to master and use the message entities described on this page in plugins, and LangBot's internal message processing logic can automatically handle message parsing and conversion.
## Message Chain
`Messages` on messaging platforms are different from messages used for AI interaction. Messages on messaging platforms are described in the form of `message chains`. Each independent message displayed on QQ is a message chain, which can contain various `message chain components` such as `text`, `images`, `@mentions`, etc. For example:
![](/assets/image/zh/plugin/dev/apis/plugin_dev_messages_01.png)
This is a message chain containing one Plain component (Hello World) and one Image component (Usagi).
The definitions of message chains and message chain components are located in `langbot_plugin.api.entities.platform.message`.
### Constructing Message Chains
Please first import the `langbot_plugin.api.entities.platform.message` package to use the message components within it.
```python
from langbot_plugin.api.entities.platform.message import *
# Build a message containing text "Hello LangBot" and an image (from URL)
msg_chain = MessageChain([
Plain(text="Hello LangBot"),
Image(url='https://qchatgpt.rockchin.top/langbot-logo.png')
])
# Build a message containing @everyone and text "Hello LangBot"
msg_chain = MessageChain([
AtAll(),
Plain(text="Hello LangBot")
])
# Build a message containing @specific member and text "Hello LangBot"
msg_chain = MessageChain([
At(target=123456),
Plain(text="Hello LangBot")
])
```
Currently supported message chain components:
- `Source` - Source message chain information. Messages received from messaging platforms will include this component at the front of the message chain to record message information
- `Plain` - Plain text message
- `Quote` - Quote message
- `Image` - Image message
- `AtAll` - @everyone message
- `At` - @specific member message
- `Voice` - Voice message
- Need to check messaging platform support
- `Forward` - Forward message
- Not supported on many platforms, not recommended
- `File` - File message
When creating components, named parameters must be used. For specific usage, you can check the definitions in the source code `langbot_plugin.api.entities.platform.message`.
## Other Entities
In addition to the above entities, there are also entities like `Friend` and `Group`. These entities are defined in `langbot_plugin.api.entities.platform.entities`.
## Accessing Underlying Platform APIs
:::warning Note
Not applicable for 4.x.
:::
To abstract away platform differences, LangBot provides an abstraction layer above messaging platform APIs, which includes the content described above and various platform adapters in the `pkg/platform/sources` directory.
However, due to significant platform differences, the entity and API encapsulation in the abstraction layer is not complete. If your plugin needs to access platform-specific functionality, you can access underlying APIs according to the following instructions.
:::info
The `adapter` here is the instantiated object of each adapter, corresponding to the `xxxAdapter(adapter.MessagePlatformAdapter)` classes in files under `pkg/platform/sources/`. It can be obtained from `query.adapter` in plugin events, for example:
```python
# For example in a plugin
@handler(GroupMessageReceived)
async def _(self, ctx: EventContext):
# Get adapter object
adapter = ctx.query.adapter
```
:::
| Platform | Adapter | Access Method | Description |
| --- | --- | --- | --- |
| OneBot v11| aiocqhttp | adapter.bot | bot object corresponds to [aiocqhttp](https://github.com/nonebot/aiocqhttp) CQHttp object, please refer to aiocqhttp documentation for detailed usage |
| QQ Official API | qqofficial | adapter.bot | bot object is the SDK object under libs/qq_official_api, for detailed usage please refer to the `send_group_text_msg` method in `QQOfficialClient` in libs/qq_official_api/api.py for HTTP request methods, please refer to QQ Official API documentation for specific interface documentation |
| WeCom (Enterprise WeChat) | wecom | adapter.bot | Refer to the `send_image` method in `WecomClient` in libs/wecom_api/api.py for HTTP request methods, please refer to WeCom API documentation for specific interface documentation |
| WeChat Official Account | officialaccount | - | Please refer to WeChat Official Account API documentation for interface documentation, credential information can be obtained from adapter.bot |
| Lark | lark | adapter.api_client | Lark SDK's API Client object, please refer to [oapi-sdk-python](https://github.com/larksuite/oapi-sdk-python) for details |
| DingTalk | dingtalk | - | Please refer to DingTalk API documentation, credential information can be obtained from adapter.bot |
| Discord | discord | adapter.bot | bot object is [Rapptz/discord.py](https://github.com/Rapptz/discord.py) Discord object, please refer to documentation for detailed usage |
| Telegram | telegram | adapter.bot | bot object is [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) Telegram object, please refer to documentation for detailed usage |
| Slack | slack | adapter.bot | bot object in libs/slack_api/api.py, please refer to its code and Slack SDK for detailed usage |
+218
View File
@@ -0,0 +1,218 @@
# Pipeline Events
:::info Table of Contents
[[toc]]
:::
LangBot plugins can register and handle pipeline events. For usage instructions, see [Component: Event Listener](/en/plugin/dev/components/event-listener).
## *MessageReceived
Triggered when any message is received in group chat or private chat.
```python
class PersonMessageReceived(BaseEventModel):
"""When any private chat message is received"""
event_name: str = "PersonMessageReceived"
launcher_type: str
"""Launcher object type (person)"""
launcher_id: typing.Union[int, str]
"""Sender ID"""
sender_id: typing.Union[int, str]
"""Sender ID"""
message_chain: platform_message.MessageChain = pydantic.Field(
serialization_alias="message_chain"
)
class GroupMessageReceived(BaseEventModel):
"""When any group chat message is received"""
event_name: str = "GroupMessageReceived"
launcher_type: str
"""Launcher object type (group)"""
launcher_id: typing.Union[int, str]
"""Group ID"""
sender_id: typing.Union[int, str]
"""Sender ID"""
message_chain: platform_message.MessageChain = pydantic.Field(
serialization_alias="message_chain"
)
```
## *NormalMessageReceived
Triggered when a group chat or private chat message is received and determined to be a message that needs to be processed by LLM (non-command message).
```python
class PersonNormalMessageReceived(BaseEventModel):
"""Triggered when a private chat normal message that should be processed is determined"""
event_name: str = "PersonNormalMessageReceived"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
text_message: str
"""Message text"""
alter: typing.Optional[str] = None
"""Modified message text"""
reply: typing.Optional[list] = None
"""Reply message component list"""
class GroupNormalMessageReceived(BaseEventModel):
"""Triggered when a group chat normal message that should be processed is determined"""
event_name: str = "GroupNormalMessageReceived"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
text_message: str
"""Message text"""
alter: typing.Optional[str] = None
"""Modified message text"""
reply: typing.Optional[list] = None
"""Reply message component list"""
```
## *CommandSent
:::warning Note
No longer recommended for use, please use [Component: Command](/en/plugin/dev/components/command) instead.
:::
Triggered when a group chat or private chat command is received.
```python
class PersonCommandSent(BaseEventModel):
"""Triggered when a private chat command that should be processed is determined"""
event_name: str = "PersonCommandSent"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
command: str
"""Command text"""
params: list[str]
"""Command parameters"""
text_message: str
"""Message text"""
is_admin: bool
"""Whether it's an administrator"""
alter: typing.Optional[str] = None
"""Modified complete command text"""
reply: typing.Optional[list] = None
"""Reply message component list"""
class GroupCommandSent(BaseEventModel):
"""Triggered when a group chat command that should be processed is determined"""
event_name: str = "GroupCommandSent"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
command: str
"""Command text"""
params: list[str]
"""Command parameters"""
text_message: str
"""Message text"""
is_admin: bool
"""Whether it's an administrator"""
alter: typing.Optional[str] = None
"""Modified complete command text"""
reply: typing.Optional[list] = None
"""Reply message component list"""
```
## NormalMessageResponded
Triggered when a message receives an LLM response.
```python
class NormalMessageResponded(BaseEventModel):
"""Triggered when replying to a normal message"""
event_name: str = "NormalMessageResponded"
launcher_type: str
launcher_id: typing.Union[int, str]
sender_id: typing.Union[int, str]
session: provider_session.Session
"""Session object"""
prefix: str
"""Reply message prefix"""
response_text: str
"""Reply message text"""
finish_reason: str
"""Response end reason"""
funcs_called: list[str]
"""List of called functions"""
reply: typing.Optional[list] = None
"""Reply message component list"""
```
## PromptPreProcessing
Triggered when building the LLM response context (prompt).
```python
class PromptPreProcessing(BaseEventModel):
"""Triggered when preprocessing prompt in session"""
event_name: str = "PromptPreProcessing"
session_name: str
default_prompt: list[provider_message.Message]
"""Scenario preset for this conversation, can be modified"""
prompt: list[provider_message.Message]
"""Existing message records for this conversation, can be modified"""
```
+11
View File
@@ -0,0 +1,11 @@
# Plugin Technical Details
## Architecture
<img width="600" src="/assets/image/zh/plugin/dev/plugin_system_arch.png" />
In version 4.0, we introduced a high-security, high-flexibility production-grade plugin system and provided developers with rich APIs and easy-to-use supporting tools.
Plugin Runtime is used to manage plugin lifecycles and coordinate interactions between LangBot and plugins. It has two operating modes: `stdio` and `websocket`. When LangBot is started directly by users (not running in a container), it uses `stdio` mode, which is common for personal users or lightweight environments. When LangBot runs in a container, it uses `websocket` mode, designed specifically for production environments.
Plugin Runtime automatically starts each installed plugin and interacts through stdio. In plugin development scenarios, developers can use the `lbp` command-line tool to start plugins and connect to the running Runtime via WebSocket for debugging.
+157
View File
@@ -0,0 +1,157 @@
# Completing Plugin Information
## Modifying the Manifest File
The `manifest.yaml` file in the plugin directory declares the basic information about the plugin, which will be displayed in the LangBot UI or plugin marketplace interfaces.
```yaml
apiVersion: v1 # Do not modify
kind: Plugin # Do not modify
metadata:
author: RockChinQ # Author, must match regex ^[a-zA-Z0-9_-]+$
name: HelloPlugin # Plugin name, used to distinguish plugins, must match regex ^[a-zA-Z0-9-]+$
repository: 'https://github.com/langbot-app/HelloPlugin' # Plugin repository URL
version: 0.1.0 # Plugin version
description:
en_US: 'Hello LangBot Plugin' # Plugin description, multilingual
zh_Hans: 'Hello LangBot Plugin'
label:
en_US: HelloPlugin # Plugin label, displayed in the interface, multilingual
zh_Hans: HelloPlugin
icon: assets/icon.svg # Plugin icon, defaults to assets/icon.svg, can be replaced with various image formats
spec:
config: [] # Plugin configuration item format
components: {} # Plugin component list, no need to modify manually
execution:
python:
path: main.py # Do not modify
attr: HelloPlugin # Do not modify
```
Plugin multilingual support follows the [RFC 4646](https://datatracker.ietf.org/doc/html/rfc4646) standard. Currently supported languages include:
- `en_US` English
- `zh_Hans` Simplified Chinese
- `zh_Hant` Traditional Chinese
- `ja_JP` Japanese
## Plugin Configuration Item Format
In the `manifest.yaml` file, fields declared in `spec.config` will be rendered by LangBot as configuration item forms for users to fill out. Plugins can retrieve user-filled configuration items through APIs provided by the SDK (see later sections).
For example:
```yaml
spec:
config:
- name: github_token # Required; configuration item name, used for retrieval in the plugin
type: string # Required; configuration item type, supports string, integer, float, boolean, select, prompt-editor, llm-model-selector, etc.
label: # Required; configuration item display name, supports multilingual. Language codes follow RFC 4646 standard.
en_US: Github Token
zh_Hans: Github Token
description: # Configuration item description, supports multilingual. Optional.
en_US: Image downloading requires a Github token
zh_Hans: 如果不填的话,图片可能会下载失败
default: '' # Configuration item default value, optional.
required: false # Whether the configuration item is required, optional.
- name: mode
type: select
...
```
The supported types and fields for configuration items are as follows:
### type: string
String type.
```yaml
- name: api_key
type: string
...
```
### type: array[string]
String array.
```yaml
- name: tags
type: array[string]
...
```
### type: integer
Integer type.
```yaml
- name: progress
type: integer
...
```
### type: float
Float type.
```yaml
- name: temperature
type: float
...
```
### type: boolean
Boolean type.
```yaml
- name: is_enabled
type: boolean
...
```
### type: select
Dropdown menu. Requires configuring `options`, which represents the dropdown menu options.
```yaml
- name: mode
type: select
options: # Dropdown options, supports multilingual.
- name: mode1 # Value
label: # Display name, supports multilingual.
en_US: Mode 1
zh_Hans: 模式 1
- name: mode2
label: # Display name, supports multilingual.
en_US: Mode 2
zh_Hans: 模式 2
...
```
### type: prompt-editor
Prompt editor. Displays a prompt editor from the pipeline configuration page, with the final result represented in OpenAI's `messages` format.
```yaml
- name: prompt
type: prompt-editor
...
```
### type: llm-model-selector
LLM model selector. Displays an LLM model selector where you can choose configured LLM models, with the final result represented as the LLM model UUID.
```yaml
- name: model
type: llm-model-selector
...
```
## What's Next
This tutorial will guide you through step-by-step completion of plugin functionality.
- Adding Components: Plugin components are the core functional units of plugins. You can [add components](/en/plugin/dev/components/add) based on your needs.
+71
View File
@@ -0,0 +1,71 @@
# Adding Components
Plugins consist of one or more components that provide different functionalities to LangBot. Currently supported component types include:
- Event Listeners (EventListener): Listen to events during pipeline execution and modify context or pipelines.
- Commands (Command): Triggered by user command messages starting with `!` (or other configured prefixes).
- Tools (Tool): Called by LLMs during execution of LangBot's built-in Local Agent.
## Adding Components
Execute the command in the plugin directory:
```bash
lbp comp <component_type>
```
For example, to add an event listener:
```bash
lbp comp EventListener
```
Follow the prompts to enter the component configuration (if any).
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
➜ HelloPlugin >
```
:::info
The CLI will generate a `components` directory in the plugin directory and create corresponding component directories within it.
```
.
├── assets
│ └── icon.svg
├── components
│ ├── __init__.py
│ └── event_listener
│ ├── __init__.py
│ ├── default.py
│ └── default.yaml
├── main.py
├── manifest.yaml
├── README.md
└── requirements.txt
```
At the same time, component discovery configuration will be added to the plugin's `manifest.yaml`:
```yaml
...
components:
EventListener:
fromDirs:
- path: components/event_listener/
...
```
To remove components, you can delete the corresponding information manually.
:::
## Component Types
For detailed usage of each component, please refer to:
- [EventListener](./event-listener.md)
- [Command](./command.md)
- [Tool](./tool.md)
+211
View File
@@ -0,0 +1,211 @@
# Component: Command
Command components are triggered by user command messages starting with `!` (or other configured prefixes). Here's an example of triggering the `!help` command:
<img width="600" src="/assets/image/zh/plugin/dev/components/command_use.png" />
## Adding Command Components
A single plugin can contain any number of commands. Execute the command `lbp comp Command` in the plugin directory and follow the prompts to enter the command configuration.
```bash
➜ HelloPlugin > lbp comp Command
Generating component Command...
Command name: info
Command description: Show information of the query
Component Command generated successfully.
组件 Command 生成成功。
```
This will generate `info.yaml` and `info.py` files in the `components/commands/` directory. The `.yaml` file defines the basic information for the `!info` command, and the `.py` file is the command handler:
```bash
➜ HelloPlugin > tree
...
├── components
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ ├── info.py
│ │ └── info.yaml
...
```
## Manifest File: Command Component
```yaml
apiVersion: v1 # Do not modify
kind: Command # Do not modify
metadata:
name: info # Command name, users will trigger this command with !info
label: # Command display name, shown in LangBot UI, supports multilingual
en_US: Info
zh_Hans: Info
description: # Command description, shown in LangBot UI, supports multilingual. Optional.
en_US: 'Show information of the query'
zh_Hans: '发送此次消息的详细信息'
spec:
execution:
python:
path: info.py # Command handler, do not modify
attr: Info # Command handler class name, matches the class name in info.py
```
## Plugin Processing
The following code is generated by default (components/command/<command_name>.py). You need to register and implement subcommand processing logic in the `initialize` method of the `Info` class.
```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"
```
Adding subcommands:
```python
...
class Info(Command):
async def initialize(self):
await super().initialize()
@self.subcommand(
name="", # Empty string represents root command
help="Show information of the query", # Command help information
usage="info", # Command usage example, displayed in command help
aliases=["i"], # Command aliases
)
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}",
)
```
In this code, the `send` function is registered as a subcommand through the `@self.subcommand` decorator, prints the command context (ExecuteContext) information, and concatenates it into a reply message.
### Subcommand Registration
`name` is the subcommand name. Leaving it empty means handling the main command. If not empty, it matches the second parameter as the command. For example:
- `!info` matches the subcommand with name=""
- `!info field` matches the subcommand with name="field"
- `!info field value` matches the subcommand with name="field", with `value` as the subcommand parameter
In subcommand functions, you can read command parameters through the `context` variable.
The effect of the above command is shown in the image:
<img width="600" src="/assets/image/zh/plugin/dev/components/info_command_demo.png" />
### Command Context
```python
class ExecuteContext(pydantic.BaseModel):
"""Single command execution context"""
query_id: int
"""Request ID"""
session: provider_session.Session
"""Session object for this message"""
command_text: str
"""Complete command text"""
command: str
"""Command name"""
crt_command: str
"""Current command
In multi-level commands, crt_command is the current command, command is the root command.
Example: !plugin on Webwlkr
When processing plugin, command is plugin, crt_command is plugin
When processing on, command is plugin, crt_command is on
"""
params: list[str]
"""Command parameters
Parameter list after splitting the entire command by spaces
"""
crt_params: list[str]
"""Current command parameters
In multi-level commands, crt_params are current command parameters, params are root command parameters.
Example: !plugin on Webwlkr
When processing plugin, params is ['on', 'Webwlkr'], crt_params is ['on', 'Webwlkr']
When processing on, params is ['on', 'Webwlkr'], crt_params is ['Webwlkr']
"""
privilege: int
"""Session privilege level"""
...
```
### Command Return Values
Command return values (CommandReturn) currently support returning text, images (image_url, image links), and errors.
```python
yield CommandReturn(
text=reply_text,
)
yield CommandReturn(
image_url=image_url,
)
```
Command processing supports returning multiple messages, so please use `yield` statements to return messages.
For specific return values, please refer to the definition of `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)
## What's Next
You have learned the basic information about command registration and command execution. Next, you can:
- Check out [Plugin Common APIs](/en/plugin/dev/apis/common)
@@ -0,0 +1,101 @@
# Component: Event Listener
LangBot's pipeline generates events during runtime for plugins to hook into and process. Each plugin can only have one event listener (components/event_listener/default.yaml), but can register any number of events within it.
## Adding Event Listener Component
Execute the command `lbp comp EventListener` in the plugin directory. Creating an event listener component doesn't require any configuration.
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
```
This will generate `default.yaml` and `default.py` files in the `components/event_listener/` directory. The `.yaml` file defines the basic information for the event listener, and the `.py` file is the event listener handler:
```bash
➜ HelloPlugin > tree
...
├── components
│ ├── __init__.py
│ └── event_listener
│ ├── __init__.py
│ ├── default.py
│ └── default.yaml
...
```
## Manifest File: Event Listener
Since each plugin can only have one event listener, no modifications are needed in the manifest file.
## Plugin Processing
The following code is generated by default (components/event_listener/default.py). You need to register and implement event processing logic in the `initialize` method of the `DefaultEventListener` class.
```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"
```
Adding listeners for specific events:
```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!"),
])
)
```
This code registers a listener for the `PersonMessageReceived` (receiving any message from private chat) event, prints the event context (EventContext) information when the event is triggered, and calls the event context API to reply with a message.
EventContext stores common context information for this event trigger, while EventContext.event is an object of PersonMessageReceived (or other corresponding event types), storing detailed information about the event.
```python
# Summary of EventContext definition
class EventContext(pydantic.BaseModel):
"""Event context, stores information for this event execution"""
...
event: pydantic.SerializeAsAny[BaseEventModel]
"""Event object, specific type is the type specified when registering the handler, can be found in events.py"""
...
```
## Event Registration
Event registration is implemented through the `@self.handler` decorator, with the decorator parameter being the event type. All monitorable events are defined in `langbot_plugin.api.entities.builtin.events`.
## What's Next
You have learned the basic information about event listeners. Next, you can:
- Check the `List of Monitorable Events` and `Event Context APIs` in the documentation [Pipeline Events](/en/plugin/dev/apis/pipeline-events)
- Check out [Plugin Common APIs](/en/plugin/dev/apis/common)
+172
View File
@@ -0,0 +1,172 @@
# Component: Tool
LangBot's built-in Local Agent calls tools to interact with the outside world during execution. Currently, adding tools is supported through both plugins and MCP.
## Adding Tool Components
A single plugin can contain any number of tools. Execute the command `lbp comp Tool` in the plugin directory and follow the prompts to enter the tool configuration.
```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 生成成功。
```
This will generate `get_weather_alerts.yaml` and `get_weather_alerts.py` files in the `components/tools/` directory. The `.yaml` file defines the basic information for the tool, and the `.py` file is the tool handler:
```bash
➜ HelloPlugin > tree
...
├── components
│ ├── __init__.py
│ └── tools
│ ├── __init__.py
│ ├── get_weather_alerts.py
│ └── get_weather_alerts.yaml
...
```
## Manifest File: Tool
```yaml
apiVersion: v1 # Do not modify
kind: Tool # Do not modify
metadata:
name: get_weather_alerts # Tool name, for LLM recognition
label:
en_US: GetWeatherAlerts # Tool display name, shown in LangBot UI, supports multilingual
zh_Hans: GetWeatherAlerts
description:
en_US: 'Get weather alerts for a US state.' # Tool description, shown in LangBot UI, supports multilingual. Optional
zh_Hans: '获取美国某个州的天气预警'
spec:
parameters: [] # Tool parameters, values generated by LLM based on conversation context
llm_prompt: 'Get weather alerts for a US state.' # Tool prompt, for LLM to determine whether to call this tool
execution:
python:
path: get_weather_alerts.py # Tool handler, do not modify
attr: GetWeatherAlerts # Tool handler class name, matches the class name in get_weather_alerts.py
```
## Plugin Processing
The following code is generated by default (components/tools/<tool_name>.py). You need to implement the calling and return logic for this tool in the `call` method of the `GetWeatherAlerts` class.
```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 {}
```
In the `call` method, implement this tool:
:::info
The weather retrieval tool example comes from MCP's [Server Writing Example](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)
```
In this tool, we need to get the `state` parameter from `params` and call the NWS API to get weather alerts for that state, so we need to define the `state` parameter in the `parameters` of the manifest file.
The `parameters` format follows [JSON Schema](https://json-schema.org/docs), is supported by [OpenAI Function Calling feature](https://platform.openai.com/docs/guides/function-calling#defining-functions), and its root type is fixed as `type: object`. Please add parameters in `properties` and add required parameter descriptions in `required`.
```yaml
...
spec:
parameters:
type: object # Do not modify
properties: # Do not modify, add parameters here
state:
type: string
description: 'Two-letter US state code (e.g. CA, NY)'
required: # Add required parameter descriptions here
- state
...
```
This tool also uses the `httpx` library, so we need to add the `httpx` dependency in `requirements.txt` in the plugin directory.
```txt
# HelloPlugin/requirements.txt
langbot-plugin # Already exists by default
httpx
```
Now execute the command `lbp run` in the plugin directory to start debugging. Configure a model that supports tool calling in LangBot and select to use that model on the corresponding pipeline to use this tool.
<img width="600" src="/assets/image/zh/plugin/dev/components/tool_use_in_chat.png" />
## What's Next
You have learned the basic usage of tools. Next, you can:
- Check out [Plugin Common APIs](/en/plugin/dev/apis/common)
-112
View File
@@ -1,112 +0,0 @@
# Message Platform Entities
:::info Table of Contents
[[toc]]
:::
LangBot supports multiple messaging platforms, but the `message entity` format of each platform is different. To shield these differences, LangBot has a unified standard. Plugin developers only need to master and use the message entities described on this page in their plugins, and LangBot's internal message processing logic can automatically complete the parsing and conversion of messages.
:::info
LangBot's message platform entities are modified based on the implementation of [YiriMirai](https://github.com/YiriMiraiProject/YiriMirai).
:::
## Message Chain
The `message` of a message platform is different from the message used for AI interaction. The message of a message platform is described in the form of a `message chain`. Each independent message displayed on QQ is a message chain, which can contain multiple `message chain components` such as `text`, `image`, `@ component`, for example:
![](/assets/image/zh/plugin/dev/plugin_dev_messages_01.png)
This is a message chain, containing one Plain component (Hello World) and one Image component (Usagi)
The definitions of message chain and message chain components are located in `pkg/platform/types/messages.py`.
### Constructing Message Chain
Please import the `pkg.platform.types` package first to use the message components.
```python
from pkg.platform.types import *
# Build a message containing the text Hello LangBot and an image (from URL)
msg_chain = MessageChain([
Plain("Hello LangBot"),
Image(url='https://qchatgpt.rockchin.top/langbot-logo.png')
])
# Build a message containing @all and the text Hello LangBot
msg_chain = MessageChain([
AtAll(),
Plain("Hello LangBot")
])
# Build a message containing @specific member and the text Hello LangBot
msg_chain = MessageChain([
At(123456),
Plain("Hello LangBot")
])
```
Currently supported message chain components:
- `Source` Source message chain information, if it is a message received from a message platform, this component will be included at the beginning of the message chain, recording message information
- `Plain` Plain text message
- `Quote` Quote message
- `Image` Image message
- `AtAll` @all members message
- `At` @specific member message
- `Voice` Voice message
- Need to check message platform support
- `Forward` Forward message
- Not supported on many platforms, not recommended to use
- `File` File message
For specific usage, please check the definitions in the source code `pkg/platform/types/messages.py`.
## Message Platform Events
These events are different from LangBot plugin events, they are an independent set of event systems for the message platform system. These events are the source events of LangBot messages, which can be obtained from each Query.message_event.
```python
# For example in a plugin
@handler(GroupMessageReceived)
async def _(self, ctx: EventContext):
# Get the message platform event
message_event = ctx.query.message_event
```
Message platform events are defined in `pkg/platform/types/events.py`.
## Other Entities
In addition to the above entities, there are also entities such as `Friend` and `Group`, which are defined in `pkg/platform/types/entities.py`.
They are generally included in the above event objects, from which you can get some information not included in the plugin events.
## Accessing Message Platform Low-Level API
In order to smooth out the differences between various platforms, LangBot provides an abstraction layer on top of the message platform APIs, namely the content described above and the platform adapters in the `pkg/platform/sources` directory.
However, due to the large differences between platforms, the entities and API encapsulation in the abstraction layer are not complete. If your plugin needs to access specific platform functions, you can access the low-level API according to the following instructions.
:::info
The `adapter` here is the instantiated object of each adapter, corresponding to the `xxxAdapter(adapter.MessagePlatformAdapter)` class in each file under `pkg/platform/sources/`. It can be obtained from the plugin event's `query.adapter`, for example:
```python
# For example in a plugin
@handler(GroupMessageReceived)
async def _(self, ctx: EventContext):
# Get the adapter object
adapter = ctx.query.adapter
```
:::
| Platform | Adapter | Access Method | Description |
| --- | --- | --- | --- |
| OneBot v11| aiocqhttp | adapter.bot | The bot object corresponds to the CQHttp object of [aiocqhttp](https://github.com/nonebot/aiocqhttp), please refer to the aiocqhttp documentation for detailed usage |
| QQ Official API | qqofficial | adapter.bot | The bot object is the SDK object under libs/qq_official_api, for detailed usage, you can refer to the way of initiating HTTP requests in the `send_group_text_msg` method in `QQOfficialClient` in libs/qq_official_api/api.py, please check the QQ official API documentation for specific interface documentation|
| WeCom | wecom | adapter.bot | Refer to the way of initiating HTTP requests in the `send_image` method in `WecomClient` in libs/wecom_api/api.py, please check the WeCom API documentation for specific interface documentation |
| WeChat Official Account | officialaccount | - | Please refer to the interface documentation information in the WeChat Official Account API documentation, the relevant credential information can be obtained from adapter.bot |
| Feishu | lark | adapter.api_client | Feishu SDK's API Client object, please refer to [oapi-sdk-python](https://github.com/larksuite/oapi-sdk-python) |
| DingTalk | dingtalk | - | Please refer to the DingTalk API documentation, the relevant credential information can be obtained from adapter.bot |
| Discord | discord | adapter.bot | The bot object is the Discord object of [Rapptz/discord.py](https://github.com/Rapptz/discord.py), please refer to the documentation for detailed usage |
| Telegram | telegram | adapter.bot | The bot object is the Telegram object of [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot), please refer to the documentation for detailed usage |
| Slack | slack | adapter.bot | The bot object is the object in libs/slack_api/api.py, please refer to its code and Slack SDK for detailed usage |
+27
View File
@@ -0,0 +1,27 @@
# Migration Guide
This tutorial highlights certain considerations. Please read the current version plugin tutorial first, then use this document as a reference for plugin migration.
## Migrating from 3.x Supported Asynchronous Plugins
:::info
Plugin characteristics:
- Main time period: 2024.2 ~ 2025.8
- Registration method: Using `@handler` decorators to register event listeners and `@llm_func` decorators to register content functions (tools) in the plugin class in `main.py`; all handler functions are asynchronous functions (async def)
:::
- Plugin and component registration method: Changed from the previous single plugin class decorator registration method to manifest file registration method. See [Manifest File](/en/plugin/dev/basic-info); and separated different types of components independently. For details, see [Adding Components](/en/plugin/dev/components/add).
- Message chain entities: Now refactored. The construction method of each message chain element now requires explicit passing of **named parameters**. See [Message Platform Entities](/en/plugin/dev/apis/messages).
- Event listeners: Provides backward compatibility, but no longer provides `query: Query` object in context. For details, see [Pipeline Events](/en/plugin/dev/apis/pipeline-events).
- Command components: Now refactored. See [Command Components](/en/plugin/dev/components/command).
## Migrating from 2.x and 3.x Supported Synchronous Plugins
:::info
Plugin characteristics:
- Main time period: 2023.1 ~ 2024.2
- Registration method: Using `@on` decorators to register event listeners and `@func` decorators to register content functions (tools) in the plugin class in `main.py`; all handler functions are synchronous functions (def)
:::
- Synchronous to asynchronous: Plugin runtime has now changed to fully asynchronous (based on asyncio). Please change all plugin code involving IO operations to asynchronous writing.
- For detailed content, please refer to the above 3.x asynchronous plugin migration guide.
+65
View File
@@ -0,0 +1,65 @@
# Publish Plugin
Publish plugins to [LangBot Plugin Marketplace](https://space.langbot.app/market) via CLI.
## Create Account on LangBot Space
LangBot Space is LangBot's centralized service platform. Please visit [LangBot Space](https://space.langbot.app), click the top-right corner to log in with GitHub and create an account.
<img width="600" src="/assets/image/zh/plugin/dev/login_space.png" />
This will redirect to GitHub authorization page, please complete authorization as prompted.
## Log in to LangBot Space Using CLI
Execute command `lbp login` in any directory:
```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...
```
Please click `Verification URL` to redirect to LangBot Space, enter `User Code` to complete login.
<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
==================================================
```
After successful login, you can continue using CLI to upload plugins.
## Upload Plugin
Please execute `lbp publish` in your plugin directory:
```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
```
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.
<img width="600" src="/assets/image/zh/plugin/dev/plugin_submission.png" />
If you have any questions, you can send an email to `[email protected]` or contact us through [Community](/en/insight/community).
+94 -269
View File
@@ -1,308 +1,133 @@
# Plugin Development Tutorial
> Please read the [Plugin Introduction page](/en/plugin/plugin-intro) first
> It is recommended to read the source code of this project first to understand the project architecture
## Architecture Foundation
> For questions and requirements, please open an issue in the repository
> **Try to solve problems on your own before asking**
In version 4.0, we introduced a high-security, high-flexibility production-grade plugin system and provided developers with rich APIs and easy-to-use supporting tools.
:::info Table of Contents
[[toc]]
<img width="600" src="/assets/image/zh/plugin/dev/plugin_system_arch.png" />
Plugin Runtime is used to manage plugin lifecycles and coordinate interactions between LangBot and plugins. It has two operating modes: `stdio` and `websocket`. When LangBot is started directly by users (not running in a container), it uses `stdio` mode, which is common for personal users or lightweight environments. When LangBot runs in a container, it uses `websocket` mode, designed specifically for production environments.
Plugin Runtime automatically starts each installed plugin and interacts through stdio. In plugin development scenarios, developers can use the `lbp` command-line tool to start plugins and connect to the running Runtime via WebSocket for debugging.
### Plugin Structure
A plugin's directory structure is similar to the following:
```bash
➜ HelloPlugin > tree
.
├── assets
│ └── icon.svg # Plugin icon, displayed on the plugin marketplace page
├── components # Plugin component directory, stores various component code and manifest files
│ ├── __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 # Plugin main program, listens to plugin lifecycle
├── manifest.yaml # Plugin manifest file, describes plugin metadata
├── README.md # Plugin documentation file, describes plugin functionality and usage, displayed on the plugin marketplace page
└── requirements.txt # Plugin dependencies file
```
The plugin class in main.py is common code for each plugin, initialized when the plugin starts and passed context information (configuration, etc.). You can implement initialization and shutdown logic for the plugin here. The plugin class inherits from `langbot_plugin.api.definition.plugin.BasePlugin` and provides [LangBot Global APIs](/en/plugin/dev/apis/common).
Each component is a core functional module of your plugin, which can be [added and removed](/en/plugin/dev/components/add) as needed. Different components are called in different situations, making it easy to extend plugin functionality later. Each component directly inherits from the various component base classes under the `langbot_plugin.api.definition.components` package and contains a `plugin: BasePlugin` object, allowing you to directly call the plugin's APIs.
::: info
- For detailed API documentation, please refer to [API Reference](/en/plugin/dev/apis/common).
Please continue reading this tutorial to understand the plugin development process.
:::
## 💬 Introduction
## Installing CLI
This page explains the steps for developing a regular plugin. Plugins have two methods: event listeners and content functions.
Event listeners can respond to certain events when triggered and change behavior. Content functions can be called by large language models, please check the plugin introduction page for details.
Please ensure you have Python 3.10 or higher installed, and have installed the [uv package manager](https://docs.astral.sh/uv/).
## 💻 Quick Start
Use [HelloPlugin](https://github.com/langbot-app/HelloPlugin) as a template to generate a plugin code repository, then clone the repository code to the `plugins` directory.
Modify the parts that need to be changed in the plugin's README.md file.
Edit `main.py` and enter the following content:
```Python
from pkg.plugin.context import register, handler, llm_func, BasePlugin, APIHost, EventContext
from pkg.plugin.events import * # Import event classes
"""
When receiving a private chat or group chat message "hello", reply with "hello, <sender id>!" or "hello, everyone!"
"""
class HelloPlugin(BasePlugin):
# Triggered when the plugin is loaded
def __init__(self, host: APIHost):
pass
# Asynchronous initialization
async def initialize(self):
pass
# Triggered when a personal message is received
@handler(PersonNormalMessageReceived)
async def person_normal_message_received(self, ctx: EventContext):
msg = ctx.event.text_message # Here event is the PersonNormalMessageReceived object
if msg == "hello": # If the message is hello
# Output debug information
self.ap.logger.debug("hello, {}".format(ctx.event.sender_id))
# Reply with message "hello, <sender id>!"
ctx.add_return("reply", ["hello, {}!".format(ctx.event.sender_id)])
# Prevent the default behavior of this event (getting a reply from the API)
ctx.prevent_default()
# Triggered when a group message is received
@handler(GroupNormalMessageReceived)
async def group_normal_message_received(self, ctx: EventContext):
msg = ctx.event.text_message # Here event is the GroupNormalMessageReceived object
if msg == "hello": # If the message is hello
# Output debug information
self.ap.logger.debug("hello, {}".format(ctx.event.sender_id))
# Reply with message "hello, everyone!"
ctx.add_return("reply", ["hello, everyone!"])
# Prevent the default behavior of this event (getting a reply from the API)
ctx.prevent_default()
# Triggered when the plugin is unloaded
def __del__(self):
pass
Execute the command in any empty directory to install LangBot CLI and SDK:
```bash
pip install langbot_plugin==0.1.1b5
```
## Initializing Plugin Directory
Assuming your plugin name is `HelloPlugin`, create a directory `HelloPlugin` in any directory, enter that directory, and execute the command to initialize the plugin:
```bash
lbp init
```
Follow the prompts to enter `Author`, `Description`, and other information.
:::info
You can also use the `lbp init HelloPlugin` command to initialize the plugin in the subdirectory `HelloPlugin`.
Interpreting this plugin program
- `import pkg.plugin.context` imports `register (used to register plugin classes)`, `handler (used to register event listeners)`, `llm_func (used to register content functions)`, `BasePlugin (plugin base class)`, `APIHost (API host)`, `EventContext (event context class)` and other content
- `import pkg.plugin.events` imports all supported event classes
- Declare class `HelloPlugin` inheriting from `BasePlugin`, this class can be named arbitrarily, the plugin name is only related to the parameter when calling `register`
- Declare the `__init__` method of this class, this method is optional, the code in it will be executed when the main program loads the plugin at startup
- The plugin class also supports adding an asynchronous method `async def initialize(self)` for asynchronous initialization
- `@handler` marks the method `person_normal_message_received` as an event listener, handling the `PersonNormalMessageReceived` (triggered when a private chat message is received and before getting an OpenAI reply) event, this method can be named arbitrarily, the bound event is only related to the parameter in `handler`, more supported events can be found in the `pkg.plugin.events` file or check the `API` section below
- Output debug information through the `self.ap.logger` logger, the `ap` object in the plugin class parent class is the context object of the entire program, you can access all objects in the program through this object
- The event listener method gets a second parameter `ctx` which is the context of this event, the `event` in it is the event object of this event, you can extract related parameters from it, for specific parameters that can be obtained from each event, please check the comments of each event class in the `pkg.plugin.events` file
- The event listener method extracts the `text_message` parameter from the parameters, checks if it is `hello`, if so, sets the return value `reply` to `["hello, {}!".format(ctx.event.sender_id)]`, then calls the `ctx.prevent_default()` method to prevent the original program's default behavior
- For return values supported by each event, please check the comments of each event in `pkg.plugin.events`
- Register the `GroupNormalMessageReceived` event with similar code to handle group messages
After writing and saving, restart the main program, go to the WebUI plugin page to see the plugin
<img width="600" src="/assets/image/zh/plugin/dev/create_plugin.png" />
:::
This plugin will implement: replying with `hello, <sender QQ number>!` when receiving a `hello` message in private chat, and replying with `hello, everyone!` when receiving a `hello` message in group chat.
### Writing the Manifest File
Starting from LangBot 4.0, manifest files will be used to register plugins.
Please create a new file `manifest.yaml` in the same directory as main.py. If it already exists, please modify it according to the format below.
```yaml
apiVersion: v1 # Do not change
kind: Plugin # Do not change
metadata:
# author and name uniquely identify a plugin
author: langbot # Plugin author, change to your name
name: Hello # Plugin name, change to your plugin name
repository: 'https://github.com/langbot-app/HelloPlugin' # Plugin repository address, change to your plugin GitHub repository address
version: 0.1.0 # Plugin version, change to your plugin version
description: # Plugin description, change to your plugin description, supports multiple languages. Language codes follow the RFC 4646 standard.
en_US: Plugin for sending hello
zh_Hans: 示例插件
label: # Plugin display name, supports multiple languages. The WebUI will display the label in the corresponding language. Language codes follow the RFC 4646 standard.
en_US: Hello
zh_Hans: Hello
spec:
# Plugin configuration (optional), can configure multiple items
config:
- name: github_token # Configuration item name
label: # Configuration item display name, supports multiple languages. Language codes follow the RFC 4646 standard.
en_US: Github Token
zh_Hans: Github Token
description: # Configuration item description, supports multiple languages. Optional.
en_US: Image downloading requires a Github token
zh_Hans: 如果未填写,图片下载可能会失败
type: string # Configuration item type, supports string, integer, float, boolean, etc.
default: '' # Configuration item default value
required: false # Whether the configuration item is required
execution:
python:
path: main.py # Plugin main program path, must be the same as the file name of the plugin entry code above
attr: HelloPlugin # Plugin class name, must be the same as the class name declared in the code above
```
This operation will generate the initial files for the plugin. You can now open the `HelloPlugin` directory in your favorite editor and start writing plugin code.
:::info
Configuration item supported types:
If you get a "command not found" error for `lbp`, it may be because you haven't properly set the `PATH` environment variable.
You can use `python -m langbot_plugin.cli.__init__` instead of the `lbp` command.
- string: String
- integer: Integer
- float: Floating point number
- boolean: Boolean value
- array[string]: String array
- select: Dropdown box, can select one option from multiple options
- Need to configure the options option, indicating the options in the dropdown box
For example:
```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: Prompt editor.
- llm-model-selector: LLM model selector.
After the user modifies the configuration items through the WebUI, the plugin can get the values of the configuration items in self.config.
```bash
python -m langbot_plugin.cli.__init__ init HelloPlugin
cd HelloPlugin
```
:::
## Starting Debug Mode
## ❗ Standards (Important)
You need to first deploy and start LangBot, ensuring that Plugin Runtime is running and listening on port `5401`.
- Please keep each plugin in a separate directory for management, it is recommended to create a repository on Github to store a single plugin for easy retrieval and updates
- Naming conventions (important):
- Plugin author: Use English, case insensitive, such as `RockChinQ`
- Plugin name: Use `PascalCase`, such as `Hello`, `ExamplePlugin`, `ChineseCommands`, etc.
- Plugin repository: Please use PascalCase, it is recommended to be the same as the plugin name, such as `HelloPlugin`
- Plugin description: It is recommended to include the phrase `LangBot Plugin`
- Multiple Python program files can be stored in one directory to separate the various functions of the plugin for easy management by developers, but it is not recommended to register multiple plugins in one directory
- Please specify the dependencies required by the plugin in the `requirements.txt` file in the plugin directory, the program will automatically install the dependencies when obtaining this plugin from the repository
::: info
Runtime has two startup modes:
## 🪝 Content Functions
- `Stdio` mode: When you start LangBot using source code without the startup parameter `--standalone-runtime`, LangBot will automatically start Plugin Runtime as a subprocess and communicate with Plugin Runtime through `stdio` (standard input/output streams). In this case, Runtime will load plugins from the `data/plugins` directory in the LangBot root directory and listen on port `5401` of the LangBot host as a debug port.
`Content functions` implemented through [GPT's Function Calling capability](https://platform.openai.com/docs/guides/gpt/function-calling), which is a function embedded in the conversation and automatically called by GPT.
- `WebSocket` mode:
- Production environment: When you start LangBot using the official `docker-compose.yaml`, Plugin Runtime will run in a separate container, and LangBot will communicate with Plugin Runtime in WebSocket mode due to the startup parameter `--standalone-runtime`.
- Development environment: If you start LangBot through source code with the startup parameter `--standalone-runtime`, LangBot will connect to the already started Plugin Runtime according to the `plugin.runtime_ws_url` address (port usually 5400) configured in `data/config.yaml`. You need to start the standalone Plugin Runtime yourself, see [Developing Plugin Runtime](/en/develop/plugin-runtime).
> Your plugin does not necessarily have to include content functions, please check the content function page to understand this feature first
Whether in `stdio` or `websocket` mode, Plugin Runtime will listen on port `5401` of its host as a debug port for plugin debugging connections.
### Content Function Writing Steps
When developing plugins, we recommend starting LangBot according to the [Development Configuration](/en/develop/dev-config) method, which will start Plugin Runtime in Stdio mode, making plugin development easier.
:::
1️⃣ Please first follow the steps above to write your plugin's basic structure, now please delete (of course you can also keep them, just for simplicity) the various class functions decorated by `@handler` in the above plugin content
Copy the `.env.example` file in the plugin directory to `.env`, and check or modify `DEBUG_RUNTIME_WS_URL` to your Plugin Runtime's WebSocket address.
<details>
<summary>Class structure after deletion</summary>
```python
class HelloPlugin(Plugin):
# Triggered when the plugin is loaded
def __init__(self, plugin_host: APIHost):
pass
# Triggered when the plugin is unloaded
def __del__(self):
pass
```bash
cp .env.example .env
```
</details>
2️⃣ Now we add the following function to the position of the functions we just deleted (as a class method)
```Python
# Function to add
@llm_func(name="access_the_web") # Set function name
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
# You need to first use
# pip install beautifulsoup4
# to install dependencies
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()
# Remove extra blank lines or lines with only \t and spaces
s = re.sub(r'\n\s*\n', '\n', s)
if len(s) >= 512: # Truncate the first 512 characters of the plain text content of the web page
return s[:512]
return s
Start plugin debugging, and you will see the plugin output:
```bash
lbp run
```
#### Please note:
<img width="600" src="/assets/image/zh/plugin/dev/launch_debug_plugin.png" />
- The function's comments must be strictly written according to the required format, please check [this document](https://github.com/RockChinQ/CallingGPT/wiki/1.-Function-Format#function-format) for the specific format
- Content functions and `event listeners decorated with @handler` can coexist in the same plugin and are both controlled by the plugin switch
- Make sure that the model you are using supports the function calling feature
And you can see this plugin has been loaded in LangBot's WebUI.
3️⃣ Now your program has web access capability, restart the program, ask the bot about online content or directly send article links requesting summaries.
<img width="600" src="/assets/image/zh/plugin/dev/debug_plugin_loaded.png" />
- This is just an example, for more efficient web access capability support plugin, please check [WebwlkrPlugin](https://github.com/RockChinQ/WebwlkrPlugin)
## What's Next
## 📦 Publishing to the Plugin Market
This tutorial will guide you through step-by-step completion of plugin functionality.
When your plugin development is complete, please upload it to GitHub and [open an issue in the LangBot repository](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). After review, it will be added to the plugin market and will be visible on the LangBot WebUI plugin page's plugin market in about half an hour.
![](/assets/image/zh/plugin/dev/plugin_dev_tutor_01.png)
## 📄 Glossary
### Description
The `ctx: EventContext` object obtained by the event listener method can be viewed in the `pkg.plugin.context` module for its structure. The `event` attribute of the `ctx` object is the event object of this event, from which you can extract related parameters. For specific parameters that can be obtained from each event, please check the comments of each event class in the `pkg.plugin.events` file.
Event return values are all **optional**, you can submit return values by calling `ctx.add_return(key: str, ret)`
### Events
If an event is an event in a request (user sending a message), its event object will contain a `query` object, which includes the context data in the processing of this request (i.e., the process of handling a user sending a message, a request process may trigger multiple events).
For all supported events, please check the comments of each event class in the `pkg.plugin.events` file.
For `message chain components`, please check [Message Platform Entities](./messages)
### BasePlugin Structure
#### self.ap
Accessing `self.ap` in the plugin's methods is an object of the `pkg.core.app.Application` class, which contains the context object of the entire program, you can access all objects in the program through this object.
#### self.host
Accessing `self.host` in the plugin's methods is an object of the `pkg.plugin.context.APIHost` class, which provides some APIs of the main program, please check its source code for details.
### API Reference
For APIs that plugins can call, please check [API Reference](./api-ref)
### Plugin Development Group
Plugin development, publishing, and discussion
- LangBot Plugin Development: 1032327138 ([Link](https://qm.qq.com/q/G7ENGTjeou))
- Modify Plugin Information: The plugin has been created with basic plugin information. Please [complete the plugin information](/en/plugin/dev/basic-info).
- Add Components: Plugin components are the core functional units of plugins. You can [add components](/en/plugin/dev/components/add) based on your needs.
+31 -45
View File
@@ -1,66 +1,52 @@
# Plugin Introduction
# LangBot Plugins
:::info Table of Contents
[[toc]]
LangBot is composed of various internal components such as Large Language Model tools, commands, messaging platform adapters, LLM requesters, and more. To meet extensibility and flexibility requirements, we have implemented a production-grade plugin system.
Each plugin runs in an independent process, managed uniformly by the Plugin Runtime.
<img width="600" src="/assets/image/zh/plugin/dev/plugin_system_arch.png" />
:::warning
Due to Python Asyncio compatibility issues, the plugin system currently cannot run properly in native Windows environments. Please use Docker or WSL2 for deployment: [System Compatibility](/en/plugin/compatibility)
:::
## Plugin Categories
## Plugin Structure
**Plugins** are extension modules for LangBot, containing two types of executable content: **event listeners** and **content functions**. Event listeners are driven by events in the main program, while content functions are driven by content generated by GPT. Both driving methods are uniformly wrapped in the plugin class.
Plugins can consist of the following components based on their specific functionality:
### Event Listeners
- Event Handlers: Listen to events during pipeline execution and modify context or pipelines.
- Commands: Triggered by user command messages starting with `!` (or other configured prefixes).
- Tools: Called by LLMs during execution of LangBot's built-in Local Agent.
Plugins listen to various events during LangBot's operation. When an event occurs, LangBot calls the listener registered by the plugin with event-related information. Plugins can register listeners to be triggered in specific situations.
More component types will be supported for plugin implementation in the future.
### Content Functions
## Installing Plugins
Content functions differ from event-triggered plugins in that during a conversation, when the conversation content matches the description of a content function, GPT specifies which function to call, and LangBot makes the call.
This is implemented based on [GPT's Function Calling capability](https://platform.openai.com/docs/guides/gpt/function-calling), which is a function embedded in the conversation and automatically called by GPT.
Currently supports uploading plugin packages and installing plugins from the plugin marketplace.
In LangBot's plugin management page, click the top-right corner to choose the installation method.
> It's like the official ChatGPT plugins
<img width="600" src="/assets/image/zh/plugin/install_from_local.png" />
For example, if we provide GPT with a function `access_the_web` and provide detailed descriptions of it and its parameters, then when we talk to GPT about content similar to the following:
Select `.lbpkg` files shared by others or downloaded from the Marketplace to install plugins.
```
Q: Please search for QQ bot projects on GitHub?
Q: Please search for some good cloud service provider websites for me?
Q: Read and summarize this article: https://zhuanlan.zhihu.com/p/607570830
Q: Search for the weather in Qingyuan today
```
Or click the Plugin Marketplace tab, select a plugin, and click install.
GPT will respond with a function call request for `access_the_web`, LangBot will call the content function provided by the plugin, and return the call result to GPT for it to generate a new response.
Of course, the function calling feature is not limited to web access, it can also implement image processing, scientific calculations, trip planning, and other functions that require function calls. Theoretically, we can implement the same functionality as `ChatGPT Plugins` through content functions.
<img width="600" src="/assets/image/zh/plugin/install_from_marketplace.png" />
## Plugin Usage
## Plugin Management
### Installation
### Plugin Configuration
> This only provides the basic installation method for plugins, for details please check each plugin's documentation or consult its developer
Some plugins may require you to fill in specific configuration items (please refer to the plugin README instructions). Click on the plugin card to enter the plugin details page and obtain and input the required information as prompted.
#### Install from Plugin GitHub Repository
<img width="600" src="/assets/image/zh/plugin/plugin_config.png" />
In the WebUI's plugin management page, click the `Install` button, enter the plugin's GitHub repository address, and click the `Install` button.
### Updating Plugins
#### From Plugin Market
Only plugins installed from the plugin marketplace can be updated.
Install from the plugin market in the WebUI's plugin management page.
<img width="600" src="/assets/image/zh/plugin/update_plugin.png" />
#### Manual Installation
## Plugin Marketplace
Place the obtained plugin program in the `plugins` directory. For specific usage, please check each plugin's documentation or consult its developer.
### Management
#### !plugin Commands
```
!plugin List all installed plugins
!plugin get <repository address> Install a plugin from a GitHub repository address (requires administrator privileges)
!plugin update all Update all plugins (requires administrator privileges, only supports plugins installed from repositories)
!plugin update <plugin name> Update the specified plugin
!plugin del <plugin name> Delete a plugin (requires administrator privileges)
!plugin on <plugin name> Enable a plugin (requires administrator privileges)
!plugin off <plugin name> Disable a plugin (requires administrator privileges)
!func List all content functions
```
The plugin marketplace can be accessed from LangBot's internal `Plugin Management` page, or you can visit the standalone site [LangBot Plugin Marketplace](https://space.langbot.app/market).
+1 -1
View File
@@ -16,7 +16,7 @@ Git 克隆本项目:
```bash
git clone https://github.com/langbot-app/LangBot
cd LangBot
cd LangBot/docker
```
启动容器:
+1 -1
View File
@@ -3,9 +3,9 @@
:::warning
1. 请使用Python 3.10.1(不包含3.10.0)及以上版本,推荐3.10.14版本,没有Python的需自行安装。
2. 不推荐此方式,后续可能不再支持手动部署,请考虑使用 Docker 部署。
3. **请勿在 Windows 系统上原生部署 LangBot,[这将导致插件不可用](/zh/plugin/compatibility)**,推荐使用 [WSL2](https://learn.microsoft.com/zh-cn/windows/wsl/install) 或 Docker 部署。
:::
## 安装主程序
1. 前往[Release](https://github.com/langbot-app/LangBot/releases)页面下载最新版本的压缩包(推荐)`langbot-xxx-all.zip`(请勿下载 Source Code,因为其中不包含 WebUI)。解压,在解压目录打开命令行(终端)
+2 -2
View File
@@ -31,7 +31,7 @@ AI 能力主要分为两个部分,选择运行器和配置运行器参数。
- 接入 Dify 的详细步骤可以[参考教程](/zh/deploy/pipelines/dify.html)。
- 接入 n8n 的详细步骤可以[参考教程](/zh/deploy/pipelines/n8n.html)。
### 对话变量
### 请求变量
当使用外部 LLMOps 平台时,LangBot 会显式传入以下参数,您可以自行在 LLMOps 平台的开始节点中添加(下图以 Dify 为例):
@@ -40,7 +40,7 @@ AI 能力主要分为两个部分,选择运行器和配置运行器参数。
- `conversation_id`:字符串,用户会话id,由 LangBot 生成。用户重置会话后,会重新生成
- `msg_create_time`:数字类型,收到此消息的时间戳(秒)
您可以[通过插件自定义任何变量](/zh/plugin/dev/api-ref.html#%E8%AE%BE%E7%BD%AE%E8%AF%B7%E6%B1%82%E5%8F%98%E9%87%8F)。
您可以[通过插件自定义任何变量](/zh/plugin/dev/apis/common.html#%E8%AE%BE%E7%BD%AE%E8%AF%B7%E6%B1%82%E5%8F%98%E9%87%8F)。
![Dify 工作流开始节点配置](/assets/image/zh/workshop/dify-service-api/dify_workflow_var.png)
+73
View File
@@ -0,0 +1,73 @@
# 调试插件运行时、CLI、SDK
::: info
请先阅读[插件教程](/zh/plugin/plugin-intro),了解插件系统。
- 插件运行时、CLI、SDK 开源在: https://github.com/langbot-app/langbot-plugin-sdk
:::
Clone 插件运行时源码:
```bash
git clone https://github.com/langbot-app/langbot-plugin-sdk
```
进入插件运行时目录,安装依赖:
```bash
cd langbot-plugin-sdk
uv sync --dev
```
此时,uv 将自动为您创建虚拟环境(.venv),若您的编辑器询问您是否使用此虚拟环境,请选择`是`。
<img width="600" src="/assets/image/zh/develop/confirm_venv.png" />
或者请您手动激活此虚拟环境:
```bash
source .venv/bin/activate
```
## 启动插件运行时
```bash
lbp rt
```
请确保`lbp`命令是该目录下的虚拟环境中的`lbp`可执行文件,或通过`python -m langbot_plugin.cli.__init__ rt`启动。
Plugin Runtime 接受以下参数:
- `--debug-only`: 不启动`data/plugins`目录下的插件,仅允许通过调试连接加载插件。
- `--ws-debug-port`: 监听的调试端口,默认是`5401`。
- `--ws-control-port`: 监听的控制端口(供 LangBot 主程序连接),默认是`5400`。
- `-s`: 使用`stdio`接受控制连接。**仅在生产环境使用**。
### 使 LangBot 连接到此运行时
在 LangBot 的`data/config.yaml`中配置`plugin.runtime_ws_url`为`ws://localhost:5400/control/ws`。
```yaml
plugin:
runtime_ws_url: ws://localhost:5400/control/ws
```
并在启动 LangBot 主程序时携带启动参数`--standalone-runtime`(如:`uv run main.py --standalone-runtime`)。
重启 LangBot,将会使用 WebSocket 连接到此运行时。
## langbot-plugin-sdk 架构
本代码库中包含以下内容:
- `langbot_plugin.api`:插件相关实体和 API 定义。
- `langbot_plugin.assets`:插件模板。
- `langbot_plugin.cli`:插件开发 CLI 工具。
- `langbot_plugin.entities`:插件系统中非 API 定义的相关实体。
- `langbot_plugin.runtime`:插件运行时和底层通信(stdio 和 websocket)实现。
## `lbp` CLI 工具
CLI 工具提供 Runtime 启动、插件初始化、插件组件管理、Marketplace 交互等功能。
详细的程序入口请查看[`langbot_plugin.cli.__init__`](https://github.com/langbot-app/langbot-plugin-sdk/blob/main/src/langbot_plugin/cli/__init__.py)。
+15
View File
@@ -0,0 +1,15 @@
# 系统兼容性
LangBot 插件系统运用了`Asyncio`和`Subprocess`等机制,这在unix-like系统(如Linux、MacOS)上运行良好,但在Windows上存在一些问题,导致插件无法正常运行。
Windows 上的 Python 异步事件循环默认使用 `ProactorEventLoop`,但该事件循环对 Stdio 的支持性缺失,而非 Docker 环境下 LangBot 依赖 Stdio 与 Plugin Runtime 通信、Plugin Runtime 也依赖 Stdio 与插件通信。
若改为使用`SelectorEventLoop`,则会导致无法正常启动 Plugin Runtime 和插件,因为`SelectorEventLoop`不支持`Subprocess`。
详细问题参阅:[Python官方文档](https://docs.python.org/zh-cn/3.13/library/asyncio-platforms.html)
## 解决方案(拟)
后续将支持在 Windows 上默认采用 Websocket 作为 LangBot 到 Plugin Runtime 、Plugin Runtime 到插件的通信方式。
现阶段请您使用 Unix-like 系统部署 LangBot,或在 Windows 上使用 Docker 或 WSL2。
-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)。
+52 -66
View File
@@ -1,66 +1,52 @@
# 插件介绍
:::info 目录
[[toc]]
:::
## 插件分类
**插件**,是LangBot的扩展模块,其中包含**事件监听器**和**内容函数**两种可执行内容。事件监听器由主程序运行中的事件驱动,而内容函数由GPT生成的内容驱动。这两种驱动方式都统一包装在插件类中。
### 事件监听器
插件监听 LangBot 运行期间的各种事件,LangBot 在事件发生时,携带事件相关信息调用插件注册的监听器。插件可以通过注册监听器以在指定情况下被触发。
### 内容函数
内容函数不同于 通过事件触发插件,而是在对话期间,当对话内容符合内容函数的描述时,由 GPT 指定要调用的函数,并由 LangBot 进行调用。
这是基于 [GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling) 实现的,这是一种嵌入对话中,由GPT自动调用的函数。
> 就是 ChatGPT官方插件 那种东西
例如我们为GPT提供一个函数`access_the_web`,并提供其详细的描述以及其参数的描述,那么当我们在与GPT对话时涉及类似以下内容时:
```
Q: 请搜索一下github上有那些QQ机器人项目?
Q: 请为我搜索一些不错的云服务商网站?
Q:阅读并总结这篇文章:https://zhuanlan.zhihu.com/p/607570830
Q:搜一下清远今天天气如何
```
GPT将会回复一个对`access_the_web`的函数调用请求,LangBot将调用插件提供的内容函数,并返回调用结果给GPT使其生成新的回复。
当然,函数调用功能不止局限于网络访问,还可以实现图片处理、科学计算、行程规划等需要调用函数的功能,理论上我们可以通过内容函数实现与`ChatGPT Plugins`相同的功能。
## 插件用法
### 安装
> 这里只是给出了插件的基本安装方法,详细需查看各插件文档或咨询其开发者
#### 从插件 GitHub 仓库安装
在 WebUI 的插件管理页面,点击 `安装` 按钮,输入插件的 GitHub 仓库地址,点击 `安装` 按钮即可。
#### 来自插件市场
从 WebUI 的插件管理页面中的插件市场中安装。
#### 手动安装
将获取到的插件程序放置到`plugins`目录下,具体使用方式请查看各插件文档或咨询其开发者。
### 管理
#### !plugin 命令
```
!plugin 列出所有已安装的插件
!plugin get <储存库地址> 从GitHub储存库地址安装插件(需要管理员权限)
!plugin update all 更新所有插件(需要管理员权限,仅支持从储存库安装的插件)
!plugin update <插件名> 更新指定插件
!plugin del <插件名> 删除插件(需要管理员权限)
!plugin on <插件名> 启用插件(需要管理员权限)
!plugin off <插件名> 禁用插件(需要管理员权限)
!func 列出所有内容函数
```
# LangBot 插件
LangBot 内部由多种组件组成,如大模型工具、命令、消息平台适配器、大模型请求器等。为了满足扩展性和灵活性的需求,我们实现了一套生产级的插件系统。
每个插件被运行在独立的进程中,由 Plugin Runtime 统一管理。
<img width="600" src="/assets/image/zh/plugin/dev/plugin_system_arch.png" />
:::warning
由于 Python Asyncio 的兼容性问题,目前无法正常在 Windows 原生环境中使用插件系统,请改为使用 Docker 或 WSL2部署:[系统兼容性](/zh/plugin/compatibility)
:::
## 插件结构
插件根据具体功能可由以下组件构成:
- 事件处理器:监听流水线执行期间的事件,对上下文或流水线进行修改。
- 命令:由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。
- 工具:供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。
后续还将支持更多组件的插件化。
## 安装插件
目前支持上传插件包和从插件市场安装插件。
在 LangBot 的插件管理页面,点击右上角即可选择安装方式
<img width="600" src="/assets/image/zh/plugin/install_from_local.png" />
选择他人分享的或从 Marketplace 下载的`.lbpkg`文件,即可安装插件。
或点击插件市场 Tab,选择插件后点击安装。
<img width="600" src="/assets/image/zh/plugin/install_from_marketplace.png" />
## 插件管理
### 插件配置项
某些插件可能会要求您填入特定的配置项(请参考插件 README 说明),请点击插件卡片进入插件详情页,按照提示获取并输入。
<img width="600" src="/assets/image/zh/plugin/plugin_config.png" />
### 更新插件
仅支持更新从插件市场安装的插件。
<img width="600" src="/assets/image/zh/plugin/update_plugin.png" />
## 插件市场
插件市场可以从 LangBot 内部的`插件管理`页面进入,也可以访问独立站点[LangBot 插件市场](https://space.langbot.app/market)。
-110
View File
@@ -1,110 +0,0 @@
# 技术信息
以下是LangBot实现原理等技术信息,贡献之前请仔细阅读
> 此文档为 LangBot 2.x 的技术文档,已过时,3.x 版本请直接查看源码。
> 请先阅读OpenAI API的相关文档 https://beta.openai.com/docs/ ,以下信息假定您已了解OpenAI模型的相关特性及其接口的调用方法。
## 术语
包含OpenAI API涉及的术语和项目中的概念的命名
括号中是程序中相应术语的命名,无括号的为抽象概念
### 模型(model)
AI模型,程序调用OpenAI的接口获取的内容均为OpenAI的模型生成的内容。
### 字符(tokens)
OpenAI定义的字符,ASCII字符为1 token,其他为2 token。
### 提示符(prompt)
i. 调用OpenAI的文字补全模型时的提示语,模型接口会根据提示语返回回复内容。程序底层会将对话内容进行封装生成提示符。调用文字补全模型时的提示符均由`user_name`(默认为`You`,可在配置文件修改)和`bot_name`(默认为`Bot`,可在配置文件修改)标记对话角色以供模型识别,以下是实例:
```
You:今天天气真不错
Bot:很高兴你喜欢今天的天气:)
You:谢谢你
Bot:不客气:)
```
补全模型调用的程序实现请查看下文`实现`节。
ii. 调用OpenAI的绘图模型时的提示语,模型会根据提示语进行绘图并返回图片URL。
### 对象
程序将单个人或单个QQ群视为一个对象,对象和模型是一次会话中的对话双方。
### 会话(session)
会话只对文字补全功能有效,绘图功能无会话概念。每个对象使用同一个会话,会话中仅有对象和模型两个角色,故群内所有的人都将被视为同一个角色与模型进行对话。
程序获取回复的本质是`文字补全`。
由于对话需要实现联系上下文,故程序会将模型与对象的对话历史记录作为`提示符`发送给OpenAI的接口以获取符合前文的回复。
而OpenAI的文字补全接口的提示符具有长度限制(默认使用的`text-davinci-003`限制为4096 tokens),
所以增加`会话`概念以管理向接口发送的提示符内容。
会话的存活时间可以在`config.py`中设置,默认为20分钟。会话过期之后会被存入数据库并重置。下一次该对象发起对话时将重启新的会话。
### 预设值、人格(default_prompt)
每个会话的预设对话信息,可在`config.py`中设置,程序会在每个会话创建时向提示符写入以下内容:
```
You:<预设信息>
Bot:好的
```
## 实现
### QQ机器人
> 程序路径:
> pkg.qqbot
- `pkg.qqbot.manager`中的`QQBotManager`实现了接收消息、调用OpenAI模块处理消息、报告审计模块记录使用量等功能,并提供通知管理员、发送消息等方法供其他模块调用。
- `pkg.qqbot.filter`提供了敏感词过滤的相关操作。
- `pkg.qqbot.process`提供了私聊消息和群聊消息的统一处理逻辑。
使用mirai及YiriMirai作为Python与QQ交互的框架,详细请见其文档。
在启动时会调用YiriMirai的函数以创建一个bot对象,用于程序通过mirai与QQ进行交互,在上层程序调用此bot对象的方法进行消息处理。
由于YiriMirai暂时无法关闭机器人,故在热重载前后维持同一个bot对象,这意味着QQ机器人的相关配置(QQ号、适配器等)信息不支持热重载。
### 数据库
> 程序路径:
> pkg.database
- `pkg.database.manager`中的`DatabaseManager`封装了诸多调用数据库的方法以供其他模块调用。
使用SQLite作为数据库,储存所有对象的历史会话信息、api-key的费用情况、api-key的使用量情况。
### OpenAI交互
> 程序路径:
> pkg.openai
- `pkg.openai.manager`中的`OpenAIInteract`类封装了OpenAI的文字补全`Completion`API和绘图API供机器人模块调用,并在接口调用成功之后向审计模块报告当前使用的api-key的使用量信息。
- `pkg.openai.keymgr`实现了多api-key的管理,其中以`exceeded`变量在运行时记录api-key的超额报错记录,并提供根据超额记录进行的api-key切换功能。
- `pkg.openai.pricing`记录各个模型的费用信息,供调用接口时估算费用,费用估算功能不再与api-key的切换挂钩,api-key仅在调用接口报错超额时进行切换。
- `pkg.openai.session`中的`Session`进行会话管理。
### utils模块
#### context模块
保存前述模块中的对象,并允许各个模块从此处获取其他模块的对象以调用其方法。
#### 热重载功能
> pkg.utils.reloader
重载前保存context中的所有对象,执行`main.py`中的程序关闭流程,使用`importlib`的`reload`函数重载所有模块(包含配置文件,包含新增的模块),重载后将context恢复,并执行程序启动流程。
所有模块都会重新创建对象,但QQ机器人模块中的bot对象不会被重新创建,这是因为YiriMirai提供的shutdown方法无法使用,这意味着`config.py`中关于QQ机器人的配置不支持热重载。
#### 热更新功能
> pkg.utils.updater
使用`dulwich`库执行pull操作拉取远程仓库的最新源码,并进行一次热重载加载最新代码。