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
+72
View File
@@ -0,0 +1,72 @@
# 添加组件
插件由一种或多种组件构成,为 LangBot 提供不同的功能。目前支持的组件类型有:
- 事件监听器(EventListener):监听流水线执行期间的事件,对上下文或流水线进行修改。
- 命令(Command):由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。
- 工具(Tool):供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。
## 添加组件
在插件目录下执行命令:
```bash
lbp comp <component_type>
```
例如,添加一个事件监听器:
```bash
lbp comp EventListener
```
请根据提示输入组件的配置(若有)。
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
➜ HelloPlugin >
```
:::info
CLI 将在插件目录下生成一个 `components` 目录,并在其中创建组件对应目录
```
.
├── assets
│   └── icon.svg
├── components
│   ├── __init__.py
│   └── event_listener
│   ├── __init__.py
│   ├── default.py
│   └── default.yaml
├── main.py
├── manifest.yaml
├── README.md
└── requirements.txt
```
同时,会在插件的`manifest.yaml`中添加组件发现配置:
```yaml
...
components:
EventListener:
fromDirs:
- path: components/event_listener/
...
```
如需删除组件,可自行删除对应信息。
:::
## 组件类型
各个组件的详细使用方式请参考:
- [EventListener](./event-listener.md)
- [Command](./command.md)
- [Tool](./tool.md)
+211
View File
@@ -0,0 +1,211 @@
# 组件:命令
命令组件由用户通过`!`(或其他已设置的前缀)开头的命令消息触发。如下是命令`!help`的触发示例:
<img width="600" src="/assets/image/zh/plugin/dev/components/command_use.png" />
## 添加命令组件
单个插件中能添加任意数量的命令,请在插件目录执行命令`lbp comp Command`,并根据提示输入命令的配置。
```bash
➜ HelloPlugin > lbp comp Command
Generating component Command...
Command name: info
Command description: Show information of the query
Component Command generated successfully.
组件 Command 生成成功。
```
现在即会在`components/commands/`目录下生成`info.yaml`和`info.py`文件,`.yaml`定义了`!info`命令的基础信息,`.py`是该命令的处理程序:
```bash
➜ HelloPlugin > tree
...
├── components
│   ├── __init__.py
│   ├── commands
│   │   ├── __init__.py
│   │   ├── info.py
│   │   └── info.yaml
...
```
## 清单文件:命令组件
```yaml
apiVersion: v1 # 请勿修改
kind: Command # 请勿修改
metadata:
name: info # 命令名称,在使用时,用户会通过 !info 触发该命令
label: # 命令显示名称,用于显示在 LangBot 的 UI 上,支持多语言
en_US: Info
zh_Hans: Info
description: # 命令描述,用于显示在 LangBot 的 UI 上,支持多语言。可选。
en_US: 'Show information of the query'
zh_Hans: '发送此次消息的详细信息'
spec:
execution:
python:
path: info.py # 命令处理程序,请勿修改
attr: Info # 命令处理程序的类名,与 info.py 中的类名一致
```
## 插件处理
默认会生成如下代码(components/command/<命令名称>.py),您需要在`Info`类的`initialize`方法中注册并实现子命令的处理逻辑。
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from typing import Any, AsyncGenerator
from langbot_plugin.api.definition.components.command.command import Command, Subcommand
from langbot_plugin.api.entities.builtin.command.context import ExecuteContext, CommandReturn
class Info(Command):
async def initialize(self):
await super().initialize()
"Fill with your code here"
```
添加子命令:
```python
...
class Info(Command):
async def initialize(self):
await super().initialize()
@self.subcommand(
name="", # 空字符串表示根命令
help="Show information of the query", # 命令帮助信息
usage="info", # 命令使用示例,显示在命令帮助信息中
aliases=["i"], # 命令别名
)
async def send(self, context: ExecuteContext) -> AsyncGenerator[CommandReturn, None]:
print(context)
reply_text = f"Query ID: {context.query_id}\n"
reply_text += f"command: {context.command}\n"
reply_text += f"command_text: {context.command_text}\n"
reply_text += f"params: {context.params}\n"
reply_text += f"crt_params: {context.crt_params}\n"
reply_text += f"privilege: {context.privilege}\n"
reply_text += f"session: {context.session.launcher_type.value}_{context.session.launcher_id}\n"
yield CommandReturn(
text=reply_text,
)
@self.subcommand(
name="field",
help="Show information of the field",
usage="info field",
aliases=["f"],
)
async def field(self, context: ExecuteContext) -> AsyncGenerator[CommandReturn, None]:
print(context)
field_name = context.crt_params[0]
field_value = getattr(context, field_name)
yield CommandReturn(
text=f"{field_name}: {field_value}",
)
```
该代码中,`send`函数通过装饰器`@self.subcommand`注册为子命令,并在其中打印了命令的上下文(ExecuteContext)信息并拼接成一条回复消息。
### 子命令注册
`name` 为子命令名称,留空表示处理主命令。不为空则匹配第二个参数为命令,例如:
- `!info` 匹配 name="" 的子命令
- `!info field` 匹配 name="field" 的子命令
- `!info field value` 匹配 name="field" 的子命令,`value` 为子命令参数
子命令函数中,可通过 `context` 变量读取到命令参数。
上述命令效果如图:
<img width="600" src="/assets/image/zh/plugin/dev/components/info_command_demo.png" />
### 命令上下文
```python
class ExecuteContext(pydantic.BaseModel):
"""单次命令执行上下文"""
query_id: int
"""请求ID"""
session: provider_session.Session
"""本次消息所属的会话对象"""
command_text: str
"""命令完整文本"""
command: str
"""命令名称"""
crt_command: str
"""当前命令
多级命令中crt_command为当前命令,command为根命令。
例如:!plugin on Webwlkr
处理到plugin时,command为plugin,crt_command为plugin
处理到on时,command为plugin,crt_command为on
"""
params: list[str]
"""命令参数
整个命令以空格分割后的参数列表
"""
crt_params: list[str]
"""当前命令参数
多级命令中crt_params为当前命令参数,params为根命令参数。
例如:!plugin on Webwlkr
处理到plugin时,params为['on', 'Webwlkr'],crt_params为['on', 'Webwlkr']
处理到on时,params为['on', 'Webwlkr'],crt_params为['Webwlkr']
"""
privilege: int
"""会话权限级别"""
...
```
### 命令返回值
命令返回值(CommandReturn)目前支持返回文本(text)和图片(image_url,图片链接)、错误(error)。
```python
yield CommandReturn(
text=reply_text,
)
yield CommandReturn(
image_url=image_url,
)
```
命令处理支持返回多条消息,故请使用`yield`语句返回消息。
具体的返回值请参考`CommandReturn`的定义:[langbot_plugin.api.entities.builtin.command.context.CommandReturn](https://github.com/langbot-app/langbot-plugin-sdk/tree/main/src/langbot_plugin/api/entities/builtin/command/context.py)
## 接下来做什么
您已经了解了命令注册和命令执行的基本信息,接下来可以:
- 查看[插件通用 API](/zh/plugin/dev/apis/common)
@@ -0,0 +1,101 @@
# 组件:事件监听器
LangBot 的流水线在运行期间会产生一些事件,供插件 Hook 并处理。每个插件中只允许添加一个事件监听器(components/event_listener/default.yaml),但在其中可以注册任意数量的事件。
## 添加事件监听器组件
请在插件目录执行命令`lbp comp EventListener`,创建事件监听器组件不需要填写任何配置。
```bash
➜ HelloPlugin > lbp comp EventListener
Generating component EventListener...
Component EventListener generated successfully.
组件 EventListener 生成成功。
```
现在即会在`components/event_listener/`目录下生成`default.yaml`和`default.py`文件,`.yaml`定义了事件监听器的基础信息,`.py`是该事件监听器的处理程序:
```bash
➜ HelloPlugin > tree
...
├── components
│   ├── __init__.py
│   └── event_listener
│   ├── __init__.py
│   ├── default.py
│   └── default.yaml
...
```
## 清单文件:事件监听器
由于每个插件仅允许添加一个事件监听器,故清单文件中不需要修改任何内容。
## 插件处理
默认会生成如下代码(components/event_listener/default.py),您需要在`DefaultEventListener`类的`initialize`方法中注册并实现事件的处理逻辑。
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from langbot_plugin.api.definition.components.common.event_listener import EventListener
from langbot_plugin.api.entities import events, context
class DefaultEventListener(EventListener):
async def initialize(self):
await super().initialize()
"Fill with your code here"
```
添加对特定事件的监听:
```python
...
class DefaultEventListener(EventListener):
async def initialize(self):
await super().initialize()
@self.handler(events.PersonMessageReceived)
async def handler(event_context: context.EventContext):
print("Hello LangBot Plugin!")
print(event_context)
await event_context.reply(
platform_message.MessageChain([
platform_message.Plain(text=f"Hello from Nahida Plugin!"),
])
)
```
此代码将注册对`PersonMessageReceived(收到来自私聊的任何消息)`事件的监听,并在事件触发时打印事件的上下文(EventContext)信息,并调用事件上下文 API 回复一条消息。
EventContext 保存了该此事件触发的通用上下文信息,而 EventContext.event 为 PersonMessageReceived (或其他对应的事件类型)的对象,保存了该事件的详细信息。
```python
# EventContext 的定义摘要
class EventContext(pydantic.BaseModel):
"""事件上下文, 保存此次事件运行的信息"""
...
event: pydantic.SerializeAsAny[BaseEventModel]
"""此次事件的对象,具体类型为handler注册时指定监听的类型,可查看events.py中的定义"""
...
```
## 事件注册
事件注册通过装饰器`@self.handler`实现,装饰器参数为事件类型。可监听的事件均在`langbot_plugin.api.entities.builtin.events`中定义。
## 接下来做什么
您已经了解了事件监听器的基本信息,接下来可以:
- 在文档[流水线事件](/zh/plugin/dev/apis/pipeline-events)中查看`可监听的事件列表`和`事件上下文 API`
- 查看[插件通用 API](/zh/plugin/dev/apis/common)
+172
View File
@@ -0,0 +1,172 @@
# 组件:工具
LangBot 内置的 Local Agent 在执行期间会根据场景调用工具以与外界交互,目前支持通过插件和 MCP 两种方式添加工具。
## 添加工具组件
单个插件中能添加任意数量的工具,请在插件目录执行命令`lbp comp Tool`,并根据提示输入工具的配置。
```bash
➜ HelloPlugin > lbp comp Tool
Generating component Tool...
Tool name: get_weather_alerts
Tool description: Get weather alerts for a US state.
Component Tool generated successfully.
组件 Tool 生成成功。
```
现在即会在`components/tools/`目录下生成`get_weather_alerts.yaml`和`get_weather_alerts.py`文件,`.yaml`定义了工具的基础信息,`.py`是该工具的处理程序:
```bash
➜ HelloPlugin > tree
...
├── components
│   ├── __init__.py
│   └── tools
│   ├── __init__.py
│   ├── get_weather_alerts.py
│   └── get_weather_alerts.yaml
...
```
## 清单文件:工具
```yaml
apiVersion: v1 # 请勿修改
kind: Tool # 请勿修改
metadata:
name: get_weather_alerts # 工具名称,供 LLM 识别
label:
en_US: GetWeatherAlerts # 工具显示名称,用于显示在 LangBot 的 UI 上,支持多语言
zh_Hans: GetWeatherAlerts
description:
en_US: 'Get weather alerts for a US state.' # 工具描述,用于显示在 LangBot 的 UI 上,支持多语言。可选
zh_Hans: '获取美国某个州的天气预警'
spec:
parameters: [] # 工具参数,由 LLM 根据对话上下文生成值
llm_prompt: 'Get weather alerts for a US state.' # 工具提示词,供 LLM 判断是否调用该工具
execution:
python:
path: get_weather_alerts.py # 工具处理程序,请勿修改
attr: GetWeatherAlerts # 工具处理程序的类名,与 get_weather_alerts.py 中的类名一致
```
## 插件处理
默认会生成如下代码(components/tools/<工具名称>.py),您需要在`GetWeatherAlerts`类的`call`方法中实现此工具的调用和返回逻辑。
```python
# Auto generated by LangBot Plugin SDK.
# Please refer to https://docs.langbot.app/en/plugin/dev/tutor.html for more details.
from __future__ import annotations
from typing import Any
from langbot_plugin.api.definition.components.tool.tool import Tool
class GetWeatherAlerts(Tool):
async def call(self, params: dict[str, Any]) -> dict[str, Any]:
"""Fill your tool code here"""
return {}
```
在 `call` 方法中,实现该工具:
:::info
天气获取工具用例来自 MCP 的 [Server 编写示例](https://modelcontextprotocol.io/quickstart/server)。
:::
```python
from __future__ import annotations
from typing import Any
from langbot_plugin.api.definition.components.tool.tool import Tool
import httpx
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json"
}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def format_alert(feature: dict) -> str:
"""Format an alert feature into a readable string."""
props = feature["properties"]
return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""
class GetWeatherAlerts(Tool):
async def call(self, params: dict[str, Any]) -> dict[str, Any]:
"""Fill your tool code here"""
state = params.get("state", "CA")
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
```
在这个工具中,我们要从`params`中获取`state`参数,并调用 NWS API 获取该州的天气预警,故我们需要在清单文件的`parameters`中定义`state`参数。
`parameters` 的格式遵循 [JSON Schema](https://json-schema.org/docs),受 [OpenAI Function Calling 特性](https://platform.openai.com/docs/guides/function-calling#defining-functions)支持,且其根类型固定为`type: object`,请在`properties`中添加参数,并在`required`中添加必填参数说明。
```yaml
...
spec:
parameters:
type: object # 请勿修改
properties: # 请勿修改,在此处添加参数
state:
type: string
description: 'Two-letter US state code (e.g. CA, NY)'
required: # 在此添加必填参数说明
- state
...
```
本工具中还使用了`httpx`库,我们需要在插件目录的`requirements.txt`中添加`httpx`依赖。
```txt
# HelloPlugin/requirements.txt
langbot-plugin # 默认已存在
httpx
```
现在在插件目录执行命令`lbp run`,启动调试。在 LangBot 中配置支持工具调用的模型,并在对应流水线上选择使用该模型,即可使用该工具。
<img width="600" src="/assets/image/zh/plugin/dev/components/tool_use_in_chat.png" />
## 接下来做什么
您已经了解了工具的基本用法,接下来可以:
- 查看[插件通用 API](/zh/plugin/dev/apis/common)