diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 1eb25355b..066d38273 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -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" }, ], }, ], diff --git a/docs/assets/image/zh/develop/confirm_venv.png b/docs/assets/image/zh/develop/confirm_venv.png new file mode 100644 index 000000000..3f66a2a87 Binary files /dev/null and b/docs/assets/image/zh/develop/confirm_venv.png differ diff --git a/docs/assets/image/zh/plugin/dev/plugin_dev_messages_01.png b/docs/assets/image/zh/plugin/dev/apis/plugin_dev_messages_01.png similarity index 100% rename from docs/assets/image/zh/plugin/dev/plugin_dev_messages_01.png rename to docs/assets/image/zh/plugin/dev/apis/plugin_dev_messages_01.png diff --git a/docs/assets/image/zh/plugin/dev/components/command_use.png b/docs/assets/image/zh/plugin/dev/components/command_use.png new file mode 100644 index 000000000..cc0f9938b Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/components/command_use.png differ diff --git a/docs/assets/image/zh/plugin/dev/components/info_command_demo.png b/docs/assets/image/zh/plugin/dev/components/info_command_demo.png new file mode 100644 index 000000000..cfb1c69d0 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/components/info_command_demo.png differ diff --git a/docs/assets/image/zh/plugin/dev/components/tool_use_in_chat.png b/docs/assets/image/zh/plugin/dev/components/tool_use_in_chat.png new file mode 100644 index 000000000..3e35e9e99 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/components/tool_use_in_chat.png differ diff --git a/docs/assets/image/zh/plugin/dev/create_plugin.png b/docs/assets/image/zh/plugin/dev/create_plugin.png new file mode 100644 index 000000000..667c295b8 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/create_plugin.png differ diff --git a/docs/assets/image/zh/plugin/dev/debug_plugin_loaded.png b/docs/assets/image/zh/plugin/dev/debug_plugin_loaded.png new file mode 100644 index 000000000..db5b86f7a Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/debug_plugin_loaded.png differ diff --git a/docs/assets/image/zh/plugin/dev/develop_comp_arch_1.png b/docs/assets/image/zh/plugin/dev/develop_comp_arch_1.png deleted file mode 100644 index cbc790f42..000000000 Binary files a/docs/assets/image/zh/plugin/dev/develop_comp_arch_1.png and /dev/null differ diff --git a/docs/assets/image/zh/plugin/dev/launch_debug_plugin.png b/docs/assets/image/zh/plugin/dev/launch_debug_plugin.png new file mode 100644 index 000000000..5d824e192 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/launch_debug_plugin.png differ diff --git a/docs/assets/image/zh/plugin/dev/login_space.png b/docs/assets/image/zh/plugin/dev/login_space.png new file mode 100644 index 000000000..3a67451c2 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/login_space.png differ diff --git a/docs/assets/image/zh/plugin/dev/plugin_dev_tutor_01.png b/docs/assets/image/zh/plugin/dev/plugin_dev_tutor_01.png deleted file mode 100644 index 865c5f4a7..000000000 Binary files a/docs/assets/image/zh/plugin/dev/plugin_dev_tutor_01.png and /dev/null differ diff --git a/docs/assets/image/zh/plugin/dev/plugin_submission.png b/docs/assets/image/zh/plugin/dev/plugin_submission.png new file mode 100644 index 000000000..e02c84b36 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/plugin_submission.png differ diff --git a/docs/assets/image/zh/plugin/dev/plugin_system_arch.png b/docs/assets/image/zh/plugin/dev/plugin_system_arch.png new file mode 100644 index 000000000..aa3e2eb73 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/plugin_system_arch.png differ diff --git a/docs/assets/image/zh/plugin/dev/space_device_flow.png b/docs/assets/image/zh/plugin/dev/space_device_flow.png new file mode 100644 index 000000000..913b5a486 Binary files /dev/null and b/docs/assets/image/zh/plugin/dev/space_device_flow.png differ diff --git a/docs/assets/image/zh/plugin/install_from_local.png b/docs/assets/image/zh/plugin/install_from_local.png new file mode 100644 index 000000000..60eb7bfc4 Binary files /dev/null and b/docs/assets/image/zh/plugin/install_from_local.png differ diff --git a/docs/assets/image/zh/plugin/install_from_marketplace.png b/docs/assets/image/zh/plugin/install_from_marketplace.png new file mode 100644 index 000000000..3d5e4fa45 Binary files /dev/null and b/docs/assets/image/zh/plugin/install_from_marketplace.png differ diff --git a/docs/assets/image/zh/plugin/plugin_config.png b/docs/assets/image/zh/plugin/plugin_config.png new file mode 100644 index 000000000..5c1d1def9 Binary files /dev/null and b/docs/assets/image/zh/plugin/plugin_config.png differ diff --git a/docs/assets/image/zh/plugin/update_plugin.png b/docs/assets/image/zh/plugin/update_plugin.png new file mode 100644 index 000000000..d0dacbe0a Binary files /dev/null and b/docs/assets/image/zh/plugin/update_plugin.png differ diff --git a/docs/en/deploy/langbot/docker.md b/docs/en/deploy/langbot/docker.md index aab4b8343..8edc6fea4 100644 --- a/docs/en/deploy/langbot/docker.md +++ b/docs/en/deploy/langbot/docker.md @@ -15,7 +15,7 @@ Clone the project: ```bash git clone https://github.com/langbot-app/LangBot -cd LangBot +cd LangBot/docker ``` Start the container: diff --git a/docs/en/deploy/langbot/manual.md b/docs/en/deploy/langbot/manual.md index d4615ec28..38f96d790 100644 --- a/docs/en/deploy/langbot/manual.md +++ b/docs/en/deploy/langbot/manual.md @@ -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 diff --git a/docs/en/deploy/pipelines/readme.md b/docs/en/deploy/pipelines/readme.md index 3f0fa87ea..f1996bcf6 100644 --- a/docs/en/deploy/pipelines/readme.md +++ b/docs/en/deploy/pipelines/readme.md @@ -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) diff --git a/docs/en/plugin/compatibility.md b/docs/en/plugin/compatibility.md new file mode 100644 index 000000000..c4a7b0114 --- /dev/null +++ b/docs/en/plugin/compatibility.md @@ -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. \ No newline at end of file diff --git a/docs/en/plugin/dev/api-ref.md b/docs/en/plugin/dev/api-ref.md deleted file mode 100644 index d6c9f11b4..000000000 --- a/docs/en/plugin/dev/api-ref.md +++ /dev/null @@ -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()) -``` diff --git a/docs/en/plugin/dev/apis/common.md b/docs/en/plugin/dev/apis/common.md new file mode 100644 index 000000000..1dd9a100b --- /dev/null +++ b/docs/en/plugin/dev/apis/common.md @@ -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..`. + +```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") +``` \ No newline at end of file diff --git a/docs/en/plugin/dev/apis/messages.md b/docs/en/plugin/dev/apis/messages.md new file mode 100644 index 000000000..de9cf2e57 --- /dev/null +++ b/docs/en/plugin/dev/apis/messages.md @@ -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 | \ No newline at end of file diff --git a/docs/en/plugin/dev/apis/pipeline-events.md b/docs/en/plugin/dev/apis/pipeline-events.md new file mode 100644 index 000000000..42ce929b4 --- /dev/null +++ b/docs/en/plugin/dev/apis/pipeline-events.md @@ -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""" +``` \ No newline at end of file diff --git a/docs/en/plugin/dev/apis/tech-details.md b/docs/en/plugin/dev/apis/tech-details.md new file mode 100644 index 000000000..82b417d2e --- /dev/null +++ b/docs/en/plugin/dev/apis/tech-details.md @@ -0,0 +1,11 @@ +# Plugin Technical Details + +## Architecture + + + +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. \ No newline at end of file diff --git a/docs/en/plugin/dev/basic-info.md b/docs/en/plugin/dev/basic-info.md new file mode 100644 index 000000000..7e662ae9c --- /dev/null +++ b/docs/en/plugin/dev/basic-info.md @@ -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. \ No newline at end of file diff --git a/docs/en/plugin/dev/components/add.md b/docs/en/plugin/dev/components/add.md new file mode 100644 index 000000000..c534aaef6 --- /dev/null +++ b/docs/en/plugin/dev/components/add.md @@ -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 +``` + +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) \ No newline at end of file diff --git a/docs/en/plugin/dev/components/command.md b/docs/en/plugin/dev/components/command.md new file mode 100644 index 000000000..e9eddb4fb --- /dev/null +++ b/docs/en/plugin/dev/components/command.md @@ -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: + + + +## 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/.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: + + + +### 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) \ No newline at end of file diff --git a/docs/en/plugin/dev/components/event-listener.md b/docs/en/plugin/dev/components/event-listener.md new file mode 100644 index 000000000..5acda0d7d --- /dev/null +++ b/docs/en/plugin/dev/components/event-listener.md @@ -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) \ No newline at end of file diff --git a/docs/en/plugin/dev/components/tool.md b/docs/en/plugin/dev/components/tool.md new file mode 100644 index 000000000..de984325f --- /dev/null +++ b/docs/en/plugin/dev/components/tool.md @@ -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/.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. + + + +## What's Next + +You have learned the basic usage of tools. Next, you can: + +- Check out [Plugin Common APIs](/en/plugin/dev/apis/common) \ No newline at end of file diff --git a/docs/en/plugin/dev/messages.md b/docs/en/plugin/dev/messages.md deleted file mode 100644 index c89f9edf9..000000000 --- a/docs/en/plugin/dev/messages.md +++ /dev/null @@ -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 | diff --git a/docs/en/plugin/dev/migration.md b/docs/en/plugin/dev/migration.md new file mode 100644 index 000000000..5fa7879a1 --- /dev/null +++ b/docs/en/plugin/dev/migration.md @@ -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. \ No newline at end of file diff --git a/docs/en/plugin/dev/publish.md b/docs/en/plugin/dev/publish.md new file mode 100644 index 000000000..72cdc1477 --- /dev/null +++ b/docs/en/plugin/dev/publish.md @@ -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. + + + +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. + + + +```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. + + + +If you have any questions, you can send an email to `hello@langbot.app` or contact us through [Community](/en/insight/community). \ No newline at end of file diff --git a/docs/en/plugin/dev/tutor.md b/docs/en/plugin/dev/tutor.md index c10c67931..cff0ee486 100644 --- a/docs/en/plugin/dev/tutor.md +++ b/docs/en/plugin/dev/tutor.md @@ -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]] + + +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, !" 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, !" - 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 + ::: -This plugin will implement: replying with `hello, !` 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. -
-Class structure after deletion - -```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 ``` -
- -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: + -- 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. + -- 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. \ No newline at end of file diff --git a/docs/en/plugin/plugin-intro.md b/docs/en/plugin/plugin-intro.md index 1b2eda605..f5527e0b4 100644 --- a/docs/en/plugin/plugin-intro.md +++ b/docs/en/plugin/plugin-intro.md @@ -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. + + + +:::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 + -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. + -## 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 + -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. + -#### 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 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 Update the specified plugin -!plugin del Delete a plugin (requires administrator privileges) -!plugin on Enable a plugin (requires administrator privileges) -!plugin off 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). \ No newline at end of file diff --git a/docs/zh/deploy/langbot/docker.md b/docs/zh/deploy/langbot/docker.md index 8d7888cf9..2a7d0077e 100644 --- a/docs/zh/deploy/langbot/docker.md +++ b/docs/zh/deploy/langbot/docker.md @@ -16,7 +16,7 @@ Git 克隆本项目: ```bash git clone https://github.com/langbot-app/LangBot -cd LangBot +cd LangBot/docker ``` 启动容器: diff --git a/docs/zh/deploy/langbot/manual.md b/docs/zh/deploy/langbot/manual.md index 04945c906..c635bef4a 100644 --- a/docs/zh/deploy/langbot/manual.md +++ b/docs/zh/deploy/langbot/manual.md @@ -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)。解压,在解压目录打开命令行(终端) diff --git a/docs/zh/deploy/pipelines/readme.md b/docs/zh/deploy/pipelines/readme.md index d1b3b874c..824257b0c 100644 --- a/docs/zh/deploy/pipelines/readme.md +++ b/docs/zh/deploy/pipelines/readme.md @@ -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) diff --git a/docs/zh/develop/plugin-runtime.md b/docs/zh/develop/plugin-runtime.md new file mode 100644 index 000000000..59ad3a748 --- /dev/null +++ b/docs/zh/develop/plugin-runtime.md @@ -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),若您的编辑器询问您是否使用此虚拟环境,请选择`是`。 + + + +或者请您手动激活此虚拟环境: + +```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)。 \ No newline at end of file diff --git a/docs/zh/plugin/compatibility.md b/docs/zh/plugin/compatibility.md new file mode 100644 index 000000000..602c61423 --- /dev/null +++ b/docs/zh/plugin/compatibility.md @@ -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。 \ No newline at end of file diff --git a/docs/zh/plugin/dev/api-ref.md b/docs/zh/plugin/dev/api-ref.md deleted file mode 100644 index 262a2c44d..000000000 --- a/docs/zh/plugin/dev/api-ref.md +++ /dev/null @@ -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()) -``` \ No newline at end of file diff --git a/docs/zh/plugin/dev/apis/common.md b/docs/zh/plugin/dev/apis/common.md new file mode 100644 index 000000000..00e2773f6 --- /dev/null +++ b/docs/zh/plugin/dev/apis/common.md @@ -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..`。 + +```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") +``` \ No newline at end of file diff --git a/docs/zh/plugin/dev/messages.md b/docs/zh/plugin/dev/apis/messages.md similarity index 71% rename from docs/zh/plugin/dev/messages.md rename to docs/zh/plugin/dev/apis/messages.md index d7569a7b9..de2dcb10a 100644 --- a/docs/zh/plugin/dev/messages.md +++ b/docs/zh/plugin/dev/apis/messages.md @@ -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。 diff --git a/docs/zh/plugin/dev/apis/pipeline-events.md b/docs/zh/plugin/dev/apis/pipeline-events.md new file mode 100644 index 000000000..772703b5e --- /dev/null +++ b/docs/zh/plugin/dev/apis/pipeline-events.md @@ -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] + """此对话现有消息记录,可修改""" +``` diff --git a/docs/zh/plugin/dev/apis/tech-details.md b/docs/zh/plugin/dev/apis/tech-details.md new file mode 100644 index 000000000..6c59f597d --- /dev/null +++ b/docs/zh/plugin/dev/apis/tech-details.md @@ -0,0 +1,11 @@ +# 插件技术细节 + +## 架构 + + + +在 4.0 版本中,我们引入了一种高安全性、高灵活性的生产级插件系统,并为插件开发者提供了丰富的 API 和易用的配套工具。 + +Plugin Runtime (插件运行时),用于管理插件的生命周期,协调 LangBot 与插件之间的交互。有两种运行模式:`stdio` 和 `websocket`。当 LangBot 是由用户直接启动(未运行在容器内时),会使用 `stdio` 模式,这种场景多为个人用户或轻量级环境。当 LangBot 运行在容器内时,会使用 `websocket` 模式,此场景专为生产级环境设计。 + +Plugin Runtime 会自动启动各个已安装的插件,通过 stdio 交互。在插件开发场景中,开发者可以使用 `lbp` 命令行工具启动插件并通过 WebSocket 连接到已启动的 Runtime 进行调试。 \ No newline at end of file diff --git a/docs/zh/plugin/dev/basic-info.md b/docs/zh/plugin/dev/basic-info.md new file mode 100644 index 000000000..7fc6764d7 --- /dev/null +++ b/docs/zh/plugin/dev/basic-info.md @@ -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)。 diff --git a/docs/zh/plugin/dev/components/add.md b/docs/zh/plugin/dev/components/add.md new file mode 100644 index 000000000..594cf3f23 --- /dev/null +++ b/docs/zh/plugin/dev/components/add.md @@ -0,0 +1,72 @@ +# 添加组件 + +插件由一种或多种组件构成,为 LangBot 提供不同的功能。目前支持的组件类型有: + +- 事件监听器(EventListener):监听流水线执行期间的事件,对上下文或流水线进行修改。 +- 命令(Command):由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。 +- 工具(Tool):供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。 + +## 添加组件 + +在插件目录下执行命令: + +```bash +lbp comp +``` + +例如,添加一个事件监听器: + +```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) \ No newline at end of file diff --git a/docs/zh/plugin/dev/components/command.md b/docs/zh/plugin/dev/components/command.md new file mode 100644 index 000000000..fb8a95181 --- /dev/null +++ b/docs/zh/plugin/dev/components/command.md @@ -0,0 +1,211 @@ +# 组件:命令 + +命令组件由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。如下是命令`!help`的触发示例: + + + +## 添加命令组件 + +单个插件中能添加任意数量的命令,请在插件目录执行命令`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` 变量读取到命令参数。 + +上述命令效果如图: + + + +### 命令上下文 + +```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) diff --git a/docs/zh/plugin/dev/components/event-listener.md b/docs/zh/plugin/dev/components/event-listener.md new file mode 100644 index 000000000..ae8df207a --- /dev/null +++ b/docs/zh/plugin/dev/components/event-listener.md @@ -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) \ No newline at end of file diff --git a/docs/zh/plugin/dev/components/tool.md b/docs/zh/plugin/dev/components/tool.md new file mode 100644 index 000000000..a714010e3 --- /dev/null +++ b/docs/zh/plugin/dev/components/tool.md @@ -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 中配置支持工具调用的模型,并在对应流水线上选择使用该模型,即可使用该工具。 + + + +## 接下来做什么 + +您已经了解了工具的基本用法,接下来可以: + +- 查看[插件通用 API](/zh/plugin/dev/apis/common) \ No newline at end of file diff --git a/docs/zh/plugin/dev/extension.md b/docs/zh/plugin/dev/extension.md deleted file mode 100644 index 6e2a64ddb..000000000 --- a/docs/zh/plugin/dev/extension.md +++ /dev/null @@ -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`为新增的运行器名称。 \ No newline at end of file diff --git a/docs/zh/plugin/dev/migration.md b/docs/zh/plugin/dev/migration.md new file mode 100644 index 000000000..f196ee6d0 --- /dev/null +++ b/docs/zh/plugin/dev/migration.md @@ -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 异步插件迁移指南。 \ No newline at end of file diff --git a/docs/zh/plugin/dev/publish.md b/docs/zh/plugin/dev/publish.md new file mode 100644 index 000000000..7ce2842b0 --- /dev/null +++ b/docs/zh/plugin/dev/publish.md @@ -0,0 +1,65 @@ +# 发布插件 + +通过 CLI 将插件发布至 [LangBot 插件市场](https://space.langbot.app/market)。 + +## 在 LangBot Space 创建账号 + +LangBot Space 是 LangBot 提供中心化服务平台。请访问[LangBot Space](https://space.langbot.app),点击右上角使用 GitHub 登录并创建账号。 + + + +这将跳转到 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`完成登录。 + + + +```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)查看您的插件提交任务。请等待审核通过后,您可以在插件市场查看您的插件。 + + + +有任何问题,您可以发送邮件到`hello@langbot.app`或通过[社区](/zh/insight/community)联系我们。 diff --git a/docs/zh/plugin/dev/tutor.md b/docs/zh/plugin/dev/tutor.md index 501ffc87c..934e3e20a 100644 --- a/docs/zh/plugin/dev/tutor.md +++ b/docs/zh/plugin/dev/tutor.md @@ -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`装饰的类函数 - -
-删除后的类结构 - -```python - -class HelloPlugin(Plugin): - - # 插件加载时触发 - def __init__(self, plugin_host: APIHost): - pass - - # 插件卸载时触发 - def __del__(self): - pass -``` - -
- -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 和易用的配套工具。 + + + +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`中初始化插件。 + + +::: + +此操作将生成插件的初始文件。您现在可以在您喜爱的编辑器中打开 `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 +``` + + + +并可以在 LangBot 的 WebUI 中看到此插件已被加载。 + + + +## 接下来做什么 + +该教程将指引您逐步完善插件功能。 + +- 修改插件信息:插件已创建,并填入了最基础的插件信息,请您[完善插件信息](/zh/plugin/dev/basic-info)。 +- 添加组件:插件组件是插件的核心功能单元,您可以根据需求[添加组件](/zh/plugin/dev/components/add)。 diff --git a/docs/zh/plugin/plugin-intro.md b/docs/zh/plugin/plugin-intro.md index 43f9a03e1..5f5590c94 100644 --- a/docs/zh/plugin/plugin-intro.md +++ b/docs/zh/plugin/plugin-intro.md @@ -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 统一管理。 + + + +:::warning +由于 Python Asyncio 的兼容性问题,目前无法正常在 Windows 原生环境中使用插件系统,请改为使用 Docker 或 WSL2部署:[系统兼容性](/zh/plugin/compatibility) +::: + +## 插件结构 + +插件根据具体功能可由以下组件构成: + +- 事件处理器:监听流水线执行期间的事件,对上下文或流水线进行修改。 +- 命令:由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。 +- 工具:供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。 + +后续还将支持更多组件的插件化。 + +## 安装插件 + +目前支持上传插件包和从插件市场安装插件。 +在 LangBot 的插件管理页面,点击右上角即可选择安装方式 + + + +选择他人分享的或从 Marketplace 下载的`.lbpkg`文件,即可安装插件。 + +或点击插件市场 Tab,选择插件后点击安装。 + + + +## 插件管理 + +### 插件配置项 + +某些插件可能会要求您填入特定的配置项(请参考插件 README 说明),请点击插件卡片进入插件详情页,按照提示获取并输入。 + + + +### 更新插件 + +仅支持更新从插件市场安装的插件。 + + + +## 插件市场 + +插件市场可以从 LangBot 内部的`插件管理`页面进入,也可以访问独立站点[LangBot 插件市场](https://space.langbot.app/market)。 diff --git a/docs/zh/plugin/tech-info.md b/docs/zh/plugin/tech-info.md deleted file mode 100644 index 1f5852027..000000000 --- a/docs/zh/plugin/tech-info.md +++ /dev/null @@ -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操作拉取远程仓库的最新源码,并进行一次热重载加载最新代码。 \ No newline at end of file