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

Feat/new plugin
This commit is contained in:
Junyan Qin (Chin)
2025-09-16 12:35:23 +08:00
committed by GitHub
parent 61eca6f343
commit 5a3cd5fcb1
59 changed files with 3246 additions and 1390 deletions
+71
View File
@@ -0,0 +1,71 @@
# Adding Components
Plugins consist of one or more components that provide different functionalities to LangBot. Currently supported component types include:
- Event Listeners (EventListener): Listen to events during pipeline execution and modify context or pipelines.
- Commands (Command): Triggered by user command messages starting with `!` (or other configured prefixes).
- Tools (Tool): Called by LLMs during execution of LangBot's built-in Local Agent.
## Adding Components
Execute the command in the plugin directory:
```bash
lbp comp <component_type>
```
For example, to add an event listener:
```bash
lbp comp EventListener
```
Follow the prompts to enter the component configuration (if any).
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
➜ HelloPlugin >
```
:::info
The CLI will generate a `components` directory in the plugin directory and create corresponding component directories within it.
```
.
├── assets
│ └── icon.svg
├── components
│ ├── __init__.py
│ └── event_listener
│ ├── __init__.py
│ ├── default.py
│ └── default.yaml
├── main.py
├── manifest.yaml
├── README.md
└── requirements.txt
```
At the same time, component discovery configuration will be added to the plugin's `manifest.yaml`:
```yaml
...
components:
EventListener:
fromDirs:
- path: components/event_listener/
...
```
To remove components, you can delete the corresponding information manually.
:::
## Component Types
For detailed usage of each component, please refer to:
- [EventListener](./event-listener.md)
- [Command](./command.md)
- [Tool](./tool.md)
+211
View File
@@ -0,0 +1,211 @@
# Component: Command
Command components are triggered by user command messages starting with `!` (or other configured prefixes). Here's an example of triggering the `!help` command:
<img width="600" src="/assets/image/zh/plugin/dev/components/command_use.png" />
## Adding Command Components
A single plugin can contain any number of commands. Execute the command `lbp comp Command` in the plugin directory and follow the prompts to enter the command configuration.
```bash
➜ HelloPlugin > lbp comp Command
Generating component Command...
Command name: info
Command description: Show information of the query
Component Command generated successfully.
组件 Command 生成成功。
```
This will generate `info.yaml` and `info.py` files in the `components/commands/` directory. The `.yaml` file defines the basic information for the `!info` command, and the `.py` file is the command handler:
```bash
➜ HelloPlugin > tree
...
├── components
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ ├── info.py
│ │ └── info.yaml
...
```
## Manifest File: Command Component
```yaml
apiVersion: v1 # Do not modify
kind: Command # Do not modify
metadata:
name: info # Command name, users will trigger this command with !info
label: # Command display name, shown in LangBot UI, supports multilingual
en_US: Info
zh_Hans: Info
description: # Command description, shown in LangBot UI, supports multilingual. Optional.
en_US: 'Show information of the query'
zh_Hans: '发送此次消息的详细信息'
spec:
execution:
python:
path: info.py # Command handler, do not modify
attr: Info # Command handler class name, matches the class name in info.py
```
## Plugin Processing
The following code is generated by default (components/command/<command_name>.py). You need to register and implement subcommand processing logic in the `initialize` method of the `Info` class.
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from typing import Any, AsyncGenerator
from langbot_plugin.api.definition.components.command.command import Command, Subcommand
from langbot_plugin.api.entities.builtin.command.context import ExecuteContext, CommandReturn
class Info(Command):
async def initialize(self):
await super().initialize()
"Fill with your code here"
```
Adding subcommands:
```python
...
class Info(Command):
async def initialize(self):
await super().initialize()
@self.subcommand(
name="", # Empty string represents root command
help="Show information of the query", # Command help information
usage="info", # Command usage example, displayed in command help
aliases=["i"], # Command aliases
)
async def send(self, context: ExecuteContext) -> AsyncGenerator[CommandReturn, None]:
print(context)
reply_text = f"Query ID: {context.query_id}\n"
reply_text += f"command: {context.command}\n"
reply_text += f"command_text: {context.command_text}\n"
reply_text += f"params: {context.params}\n"
reply_text += f"crt_params: {context.crt_params}\n"
reply_text += f"privilege: {context.privilege}\n"
reply_text += f"session: {context.session.launcher_type.value}_{context.session.launcher_id}\n"
yield CommandReturn(
text=reply_text,
)
@self.subcommand(
name="field",
help="Show information of the field",
usage="info field",
aliases=["f"],
)
async def field(self, context: ExecuteContext) -> AsyncGenerator[CommandReturn, None]:
print(context)
field_name = context.crt_params[0]
field_value = getattr(context, field_name)
yield CommandReturn(
text=f"{field_name}: {field_value}",
)
```
In this code, the `send` function is registered as a subcommand through the `@self.subcommand` decorator, prints the command context (ExecuteContext) information, and concatenates it into a reply message.
### Subcommand Registration
`name` is the subcommand name. Leaving it empty means handling the main command. If not empty, it matches the second parameter as the command. For example:
- `!info` matches the subcommand with name=""
- `!info field` matches the subcommand with name="field"
- `!info field value` matches the subcommand with name="field", with `value` as the subcommand parameter
In subcommand functions, you can read command parameters through the `context` variable.
The effect of the above command is shown in the image:
<img width="600" src="/assets/image/zh/plugin/dev/components/info_command_demo.png" />
### Command Context
```python
class ExecuteContext(pydantic.BaseModel):
"""Single command execution context"""
query_id: int
"""Request ID"""
session: provider_session.Session
"""Session object for this message"""
command_text: str
"""Complete command text"""
command: str
"""Command name"""
crt_command: str
"""Current command
In multi-level commands, crt_command is the current command, command is the root command.
Example: !plugin on Webwlkr
When processing plugin, command is plugin, crt_command is plugin
When processing on, command is plugin, crt_command is on
"""
params: list[str]
"""Command parameters
Parameter list after splitting the entire command by spaces
"""
crt_params: list[str]
"""Current command parameters
In multi-level commands, crt_params are current command parameters, params are root command parameters.
Example: !plugin on Webwlkr
When processing plugin, params is ['on', 'Webwlkr'], crt_params is ['on', 'Webwlkr']
When processing on, params is ['on', 'Webwlkr'], crt_params is ['Webwlkr']
"""
privilege: int
"""Session privilege level"""
...
```
### Command Return Values
Command return values (CommandReturn) currently support returning text, images (image_url, image links), and errors.
```python
yield CommandReturn(
text=reply_text,
)
yield CommandReturn(
image_url=image_url,
)
```
Command processing supports returning multiple messages, so please use `yield` statements to return messages.
For specific return values, please refer to the definition of `CommandReturn`: [langbot_plugin.api.entities.builtin.command.context.CommandReturn](https://github.com/langbot-app/langbot-plugin-sdk/tree/main/src/langbot_plugin/api/entities/builtin/command/context.py)
## What's Next
You have learned the basic information about command registration and command execution. Next, you can:
- Check out [Plugin Common APIs](/en/plugin/dev/apis/common)
@@ -0,0 +1,101 @@
# Component: Event Listener
LangBot's pipeline generates events during runtime for plugins to hook into and process. Each plugin can only have one event listener (components/event_listener/default.yaml), but can register any number of events within it.
## Adding Event Listener Component
Execute the command `lbp comp EventListener` in the plugin directory. Creating an event listener component doesn't require any configuration.
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
```
This will generate `default.yaml` and `default.py` files in the `components/event_listener/` directory. The `.yaml` file defines the basic information for the event listener, and the `.py` file is the event listener handler:
```bash
➜ HelloPlugin > tree
...
├── components
│ ├── __init__.py
│ └── event_listener
│ ├── __init__.py
│ ├── default.py
│ └── default.yaml
...
```
## Manifest File: Event Listener
Since each plugin can only have one event listener, no modifications are needed in the manifest file.
## Plugin Processing
The following code is generated by default (components/event_listener/default.py). You need to register and implement event processing logic in the `initialize` method of the `DefaultEventListener` class.
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from langbot_plugin.api.definition.components.common.event_listener import EventListener
from langbot_plugin.api.entities import events, context
class DefaultEventListener(EventListener):
async def initialize(self):
await super().initialize()
"Fill with your code here"
```
Adding listeners for specific events:
```python
...
class DefaultEventListener(EventListener):
async def initialize(self):
await super().initialize()
@self.handler(events.PersonMessageReceived)
async def handler(event_context: context.EventContext):
print("Hello LangBot Plugin!")
print(event_context)
await event_context.reply(
platform_message.MessageChain([
platform_message.Plain(text=f"Hello from Nahida Plugin!"),
])
)
```
This code registers a listener for the `PersonMessageReceived` (receiving any message from private chat) event, prints the event context (EventContext) information when the event is triggered, and calls the event context API to reply with a message.
EventContext stores common context information for this event trigger, while EventContext.event is an object of PersonMessageReceived (or other corresponding event types), storing detailed information about the event.
```python
# Summary of EventContext definition
class EventContext(pydantic.BaseModel):
"""Event context, stores information for this event execution"""
...
event: pydantic.SerializeAsAny[BaseEventModel]
"""Event object, specific type is the type specified when registering the handler, can be found in events.py"""
...
```
## Event Registration
Event registration is implemented through the `@self.handler` decorator, with the decorator parameter being the event type. All monitorable events are defined in `langbot_plugin.api.entities.builtin.events`.
## What's Next
You have learned the basic information about event listeners. Next, you can:
- Check the `List of Monitorable Events` and `Event Context APIs` in the documentation [Pipeline Events](/en/plugin/dev/apis/pipeline-events)
- Check out [Plugin Common APIs](/en/plugin/dev/apis/common)
+172
View File
@@ -0,0 +1,172 @@
# Component: Tool
LangBot's built-in Local Agent calls tools to interact with the outside world during execution. Currently, adding tools is supported through both plugins and MCP.
## Adding Tool Components
A single plugin can contain any number of tools. Execute the command `lbp comp Tool` in the plugin directory and follow the prompts to enter the tool configuration.
```bash
➜ HelloPlugin > lbp comp Tool
Generating component Tool...
Tool name: get_weather_alerts
Tool description: Get weather alerts for a US state.
Component Tool generated successfully.
组件 Tool 生成成功。
```
This will generate `get_weather_alerts.yaml` and `get_weather_alerts.py` files in the `components/tools/` directory. The `.yaml` file defines the basic information for the tool, and the `.py` file is the tool handler:
```bash
➜ HelloPlugin > tree
...
├── components
│ ├── __init__.py
│ └── tools
│ ├── __init__.py
│ ├── get_weather_alerts.py
│ └── get_weather_alerts.yaml
...
```
## Manifest File: Tool
```yaml
apiVersion: v1 # Do not modify
kind: Tool # Do not modify
metadata:
name: get_weather_alerts # Tool name, for LLM recognition
label:
en_US: GetWeatherAlerts # Tool display name, shown in LangBot UI, supports multilingual
zh_Hans: GetWeatherAlerts
description:
en_US: 'Get weather alerts for a US state.' # Tool description, shown in LangBot UI, supports multilingual. Optional
zh_Hans: '获取美国某个州的天气预警'
spec:
parameters: [] # Tool parameters, values generated by LLM based on conversation context
llm_prompt: 'Get weather alerts for a US state.' # Tool prompt, for LLM to determine whether to call this tool
execution:
python:
path: get_weather_alerts.py # Tool handler, do not modify
attr: GetWeatherAlerts # Tool handler class name, matches the class name in get_weather_alerts.py
```
## Plugin Processing
The following code is generated by default (components/tools/<tool_name>.py). You need to implement the calling and return logic for this tool in the `call` method of the `GetWeatherAlerts` class.
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from typing import Any
from langbot_plugin.api.definition.components.tool.tool import Tool
class GetWeatherAlerts(Tool):
async def call(self, params: dict[str, Any]) -> dict[str, Any]:
"""Fill your tool code here"""
return {}
```
In the `call` method, implement this tool:
:::info
The weather retrieval tool example comes from MCP's [Server Writing Example](https://modelcontextprotocol.io/quickstart/server).
:::
```python
from __future__ import annotations
from typing import Any
from langbot_plugin.api.definition.components.tool.tool import Tool
import httpx
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json"
}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def format_alert(feature: dict) -> str:
"""Format an alert feature into a readable string."""
props = feature["properties"]
return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""
class GetWeatherAlerts(Tool):
async def call(self, params: dict[str, Any]) -> dict[str, Any]:
"""Fill your tool code here"""
state = params.get("state", "CA")
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
```
In this tool, we need to get the `state` parameter from `params` and call the NWS API to get weather alerts for that state, so we need to define the `state` parameter in the `parameters` of the manifest file.
The `parameters` format follows [JSON Schema](https://json-schema.org/docs), is supported by [OpenAI Function Calling feature](https://platform.openai.com/docs/guides/function-calling#defining-functions), and its root type is fixed as `type: object`. Please add parameters in `properties` and add required parameter descriptions in `required`.
```yaml
...
spec:
parameters:
type: object # Do not modify
properties: # Do not modify, add parameters here
state:
type: string
description: 'Two-letter US state code (e.g. CA, NY)'
required: # Add required parameter descriptions here
- state
...
```
This tool also uses the `httpx` library, so we need to add the `httpx` dependency in `requirements.txt` in the plugin directory.
```txt
# HelloPlugin/requirements.txt
langbot-plugin # Already exists by default
httpx
```
Now execute the command `lbp run` in the plugin directory to start debugging. Configure a model that supports tool calling in LangBot and select to use that model on the corresponding pipeline to use this tool.
<img width="600" src="/assets/image/zh/plugin/dev/components/tool_use_in_chat.png" />
## What's Next
You have learned the basic usage of tools. Next, you can:
- Check out [Plugin Common APIs](/en/plugin/dev/apis/common)