mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-11-04 14:52:12 +08:00
Add 'docs/' from commit '51dddcb3f891d60bc38645e88b2dbd515344da81'
git-subtree-dir: docs git-subtree-mainline:0582d1b3d4git-subtree-split:51dddcb3f8
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: "コンポーネントの追加"
|
||||
---
|
||||
|
||||
プラグインは、LangBotにさまざまな機能を提供する1つ以上のコンポーネントで構成されています。現在サポートされているコンポーネントタイプには次のものがあります:
|
||||
|
||||
- プラグインプロセッサー(Runner):インスタンスを作成してイベントに紐付け、プラグインのコードで処理します。[チュートリアル](/ja/plugin/dev/components/runner)を参照してください。
|
||||
- Agent ランナー(Runner):Agent の設定で選択する実行コンポーネントです。独立したプラグインプロセッサーとは異なります。
|
||||
- イベントリスナー(EventListener): パイプライン実行中のイベントをリッスンし、コンテキストやパイプラインを変更します。
|
||||
- コマンド(Command): `!`(または他の設定されたプレフィックス)で始まるユーザーコマンドメッセージによってトリガーされます。
|
||||
- ツール(Tool): LangBotの組み込みLocal Agentの実行中にLLMによって呼び出されます。
|
||||
- RAGエンジン(KnowledgeEngine): LangBotにナレッジベースのインデックス作成と検索機能を提供し、ドキュメントの取り込み、検索、削除を管理します。
|
||||
- パーサー(Parser): ドキュメント取り込みの前にPDF、Wordなどのバイナリファイルを構造化テキストに解析し、RAGエンジンで使用できるようにします。
|
||||
- ページ(Page): LangBot WebUIのサイドバーにカスタムビジュアルページを登録し、管理パネルやデータ表示などのインタラクティブなインターフェースを提供します。
|
||||
|
||||
## コンポーネントの追加
|
||||
|
||||
プラグインディレクトリでコマンドを実行します:
|
||||
|
||||
```bash
|
||||
lbp comp <component_type>
|
||||
```
|
||||
|
||||
例えば、イベントリスナーを追加する場合:
|
||||
|
||||
```bash
|
||||
lbp comp EventListener
|
||||
```
|
||||
|
||||
プロンプトに従って、コンポーネント設定(ある場合)を入力します。
|
||||
|
||||
```bash
|
||||
➜ HelloPlugin > lbp comp EventListener
|
||||
Generating component EventListener...
|
||||
Component EventListener generated successfully.
|
||||
組件 EventListener 生成成功。
|
||||
➜ HelloPlugin >
|
||||
```
|
||||
|
||||
<Info title="">
|
||||
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/
|
||||
...
|
||||
```
|
||||
コンポーネントを削除するには、対応する情報を手動で削除できます。
|
||||
</Info>
|
||||
|
||||
## コンポーネントタイプ
|
||||
|
||||
各コンポーネントの詳細な使用方法については、次を参照してください:
|
||||
|
||||
- [Runner](./runner)
|
||||
- [EventListener](./event-listener)
|
||||
- [Command](./command)
|
||||
- [Tool](./tool)
|
||||
- [KnowledgeEngine](./knowledge-engine)
|
||||
- [Parser](./parser)
|
||||
- [Page](./page)
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: "コンポーネント: Command"
|
||||
---
|
||||
|
||||
Commandコンポーネントは、`!`(または他の設定されたプレフィックス)で始まるユーザーコマンドメッセージによってトリガーされます。`!help`コマンドをトリガーする例を次に示します:
|
||||
|
||||
<img width="600" src="/images/zh/plugin/dev/components/command_use.png" />
|
||||
|
||||
## Commandコンポーネントの追加
|
||||
|
||||
1つのプラグインには任意の数のコマンドを含めることができます。プラグインディレクトリでコマンド`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
|
||||
...
|
||||
```
|
||||
|
||||
## マニフェストファイル: Commandコンポーネント
|
||||
|
||||
```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/<command_name>.py`)。`Info`クラスの`initialize`メソッドでサブコマンド処理ロジックを登録して実装する必要があります。
|
||||
|
||||
```python
|
||||
# Auto generated by LangBot Plugin SDK.
|
||||
# Please refer to https://langbot.app/docs/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}",
|
||||
)
|
||||
```
|
||||
|
||||
このコードでは、`@self.subcommand`デコレータを通じて`send`関数がサブコマンドとして登録され、コマンドコンテキスト(ExecuteContext)情報を出力し、それを返信メッセージに連結します。
|
||||
|
||||
### サブコマンド登録
|
||||
|
||||
`name`はサブコマンド名です。空のままにすると、メインコマンドを処理することを意味します。空でない場合、2番目のパラメータをコマンドとして照合します。例えば:
|
||||
|
||||
- `!info`は、name=""のサブコマンドに一致
|
||||
- `!info field`は、name="field"のサブコマンドに一致
|
||||
- `!info field value`は、name="field"のサブコマンドに一致し、`value`がサブコマンドパラメータ
|
||||
- 特に、`name="*"`は、一致しないすべての第1レベルサブコマンドに一致し、`info`の後の各セクションをパラメータとして渡します。例えば`!info 123`、`!info abc`。ユーザーの入力に応じて、`context.crt_params`から`['123']`または`['abc']`を取得できます。
|
||||
|
||||
サブコマンド関数では、`context`変数を通じてコマンドパラメータを読み取ることができます。
|
||||
|
||||
上記のコマンドの効果は画像に示されています:
|
||||
|
||||
<img width="600" src="/images/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)は、現在テキスト、画像(image_url、画像リンク)、エラーの返却をサポートしています。
|
||||
|
||||
```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)
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
1 つのコマンドは**複数の会話から同時に**起動されます:
|
||||
|
||||
- 設定は呼び出しごとに読み取ってください。`initialize()` で読み取ってインスタンスにキャッシュしないでください。
|
||||
- コマンド引数、戻り値、会話データをインスタンスフィールドやモジュールレベルの変数に残さないでください。`ExecuteContext` は今回の実行でのみ有効です。
|
||||
- 同時起動ではインスタンスフィールドの書き込みが交錯します。「現在の会話」のような状態をそこに保存しないでください。呼び出しをまたいで保持するデータは Host ストレージに置き、キーに会話の識別子を含めてください。
|
||||
- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
コマンド登録とコマンド実行の基本情報を学びました。次は:
|
||||
|
||||
- 完全な [`ExecuteContext` API リファレンス](/ja/plugin/dev/apis/agent-run#command)を確認します。
|
||||
- [LangBot API](/ja/plugin/dev/apis/common)を確認します。
|
||||
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "コンポーネント: イベントリスナー"
|
||||
---
|
||||
|
||||
<Info>
|
||||
このコンポーネントはパイプラインの処理を拡張します。メッセージ受信やメンバー参加などの Bot イベントを処理する場合は、[イベントプロセッサー](/ja/plugin/dev/components/runner)を参照してください。
|
||||
</Info>
|
||||
|
||||
LangBotのパイプラインは実行時にイベントを生成し、プラグインがフックして処理できるようにします。各プラグインは1つのイベントリスナー(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
|
||||
...
|
||||
```
|
||||
|
||||
## マニフェストファイル: イベントリスナー
|
||||
|
||||
各プラグインは1つのイベントリスナーのみを持つことができるため、マニフェストファイルでの変更は必要ありません。
|
||||
|
||||
## プラグイン処理
|
||||
|
||||
以下のコードがデフォルトで生成されます(components/event_listener/default.py)。`DefaultEventListener`クラスの`initialize`メソッドでイベント処理ロジックを登録して実装する必要があります。
|
||||
|
||||
```python
|
||||
# Auto generated by LangBot Plugin SDK.
|
||||
# Please refer to https://langbot.app/docs/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]
|
||||
"""イベントオブジェクト、具体的な型はハンドラ登録時に指定された型、events.pyで見つけることができます"""
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
## イベント登録
|
||||
|
||||
イベント登録は`@self.handler`デコレータを通じて実装され、デコレータのパラメータはイベントタイプです。監視可能なすべてのイベントは`langbot_plugin.api.entities.builtin.events`で定義されています。
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
同じイベントリスナーは**複数の Workspace、複数のイベントから同時に**呼び出されます:
|
||||
|
||||
- イベント単位の状態(カウンタ、直近のイベント、会話マップなど)をインスタンスに蓄積しないでください。同時実行のコールバックが互いを上書きします。
|
||||
- コールバックは現在の `EventContext` からのみデータを取得し、コンテキストやそのフィールドを呼び出しをまたいで再利用しないでください。
|
||||
- 呼び出しをまたいで保持するデータは Host ストレージに置き、キーに installation の識別子とイベントに関連する識別子(Workspace、会話など)を含めてください。
|
||||
- installation 単位でプロセス内状態を保持する場合は、`on_installation_revoked(binding)` で解放してください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
イベントリスナーの基本情報を学びました。次は:
|
||||
|
||||
- [パイプラインイベント](/ja/plugin/dev/apis/pipeline-events)でイベントの種類とフィールドを確認します。
|
||||
- 完全な [`EventContext` API リファレンス](/ja/plugin/dev/apis/agent-run#eventlistener)を確認します。
|
||||
- [LangBot API](/ja/plugin/dev/apis/common)を確認します。
|
||||
@@ -0,0 +1,491 @@
|
||||
---
|
||||
title: "コンポーネント: 知識エンジン"
|
||||
---
|
||||
|
||||
RAGエンジン(KnowledgeEngine)コンポーネントにより、プラグインはLangBotにナレッジベースの完全なインデックス作成と検索機能を提供できます。ユーザーがLangBotでナレッジベースを作成する際、プラグインが提供するRAGエンジンを選択して、ドキュメントの取り込み、検索、削除を管理できます。また、プラグインを通じてDify、RAGFlow、FastGPTなどの業界先進のナレッジベースサービスとブリッジすることも可能です。
|
||||
|
||||
## RAGエンジンコンポーネントの追加
|
||||
|
||||
1つのプラグインには任意の数のRAGエンジンを追加できます。プラグインディレクトリでコマンド`lbp comp KnowledgeEngine`を実行し、プロンプトに従ってRAGエンジンの設定を入力します。
|
||||
|
||||
```bash
|
||||
➜ MyRAGPlugin > lbp comp KnowledgeEngine
|
||||
Generating component KnowledgeEngine...
|
||||
Knowledge Engine name: simple_rag
|
||||
Knowledge Engine description: A simple Knowledge Engine with built-in chunking and retrieval
|
||||
Component KnowledgeEngine generated successfully.
|
||||
組件 KnowledgeEngine 生成成功。
|
||||
```
|
||||
|
||||
これにより、`components/knowledge_engine/`ディレクトリに`simple_rag.yaml`と`simple_rag.py`ファイルが生成されます。`.yaml`ファイルはRAGエンジンの基本情報と設定スキーマを定義し、`.py`ファイルはこのエンジンのハンドラです:
|
||||
|
||||
```bash
|
||||
➜ MyRAGPlugin > tree
|
||||
...
|
||||
├── components
|
||||
│ ├── __init__.py
|
||||
│ └── knowledge_engine
|
||||
│ ├── __init__.py
|
||||
│ ├── simple_rag.py
|
||||
│ └── simple_rag.yaml
|
||||
...
|
||||
```
|
||||
|
||||
## マニフェストファイル: RAGエンジン
|
||||
|
||||
```yaml
|
||||
apiVersion: v1 # 変更しないでください
|
||||
kind: KnowledgeEngine # 変更しないでください
|
||||
metadata:
|
||||
name: simple_rag # RAGエンジン名、このエンジンを識別するために使用
|
||||
label:
|
||||
en_US: Simple Knowledge Engine # エンジン表示名、LangBotのUIに表示、多言語対応
|
||||
zh_Hans: 简易 RAG 引擎
|
||||
ja_JP: シンプル RAG エンジン
|
||||
description:
|
||||
en_US: 'A simple Knowledge Engine with built-in chunking and retrieval'
|
||||
zh_Hans: '内置分块和检索能力的简易 RAG 引擎'
|
||||
ja_JP: 'チャンキングと検索を内蔵したシンプルなRAGエンジン'
|
||||
spec:
|
||||
creation_schema: # ナレッジベース作成時にユーザーが入力する設定パラメータ
|
||||
- name: chunk_size
|
||||
label:
|
||||
en_US: Chunk Size
|
||||
zh_Hans: 分块大小
|
||||
type: integer
|
||||
required: false
|
||||
default: 500
|
||||
- name: chunk_overlap
|
||||
label:
|
||||
en_US: Chunk Overlap
|
||||
zh_Hans: 分块重叠
|
||||
type: integer
|
||||
required: false
|
||||
default: 50
|
||||
retrieval_schema: # 検索時に設定可能なパラメータ
|
||||
- name: score_threshold
|
||||
label:
|
||||
en_US: Score Threshold
|
||||
zh_Hans: 分数阈值
|
||||
type: float
|
||||
required: false
|
||||
default: 0.5
|
||||
execution:
|
||||
python:
|
||||
path: simple_rag.py # エンジンハンドラ、変更しないでください
|
||||
attr: SimpleRag # エンジンハンドラのクラス名、simple_rag.pyのクラス名と一致
|
||||
```
|
||||
|
||||
設定項目フォーマットのリファレンスについては、[プラグインマニフェスト設定フォーマット](/ja/plugin/dev/basic-info)を参照してください。
|
||||
|
||||
### creation_schema と retrieval_schema
|
||||
|
||||
旧バージョンの`KnowledgeRetriever`コンポーネントが単一の`spec.config`を使用していたのとは異なり、KnowledgeEngineは2つの独立したスキーマを使用します:
|
||||
|
||||
- **creation_schema**: ナレッジベース作成時にユーザーが入力するパラメータ。取り込みと検索時に`creation_settings`を通じてプラグインに渡されます。
|
||||
- **retrieval_schema**: ナレッジベースのクエリ時にユーザーが調整できるパラメータ。`retrieval_settings`を通じてプラグインに渡されます。
|
||||
|
||||
## 能力宣言
|
||||
|
||||
KnowledgeEngineはサポートする能力を宣言でき、LangBotはこれらの能力宣言に基づいてUIの表示と利用可能な操作を決定します:
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability
|
||||
|
||||
class SimpleRag(KnowledgeEngine):
|
||||
|
||||
@classmethod
|
||||
def get_capabilities(cls) -> list[str]:
|
||||
return [
|
||||
KnowledgeEngineCapability.DOC_INGESTION, # ドキュメントのアップロードと処理をサポート
|
||||
]
|
||||
```
|
||||
|
||||
利用可能な能力定数:
|
||||
|
||||
| 能力 | 説明 |
|
||||
|------|------|
|
||||
| `DOC_INGESTION` | ドキュメントのアップロードと処理をサポート。宣言すると、LangBotはナレッジベースの詳細に「ドキュメント」タブを表示します。プラグインが他のナレッジベースサービスとのブリッジ用である場合や、ユーザーによるドキュメントのアップロードが不要な場合は、この能力宣言を省略できます。 |
|
||||
| `DOC_PARSING` | ネイティブドキュメント解析(ファイルからテキストへの抽出)をサポート。宣言すると、ユーザーはドキュメントアップロード時にRAGエンジンの内蔵解析機能を選択でき、外部[Parserプラグイン](./parser)が不要になります。未宣言の場合、ドキュメントをアップロードするには外部Parserプラグインのインストールが必要です。 |
|
||||
|
||||
<Info>
|
||||
その他の検索動作(リランキング、ハイブリッド検索など)はプラグインの`retrieval_schema`で制御され、能力フラグは不要です。
|
||||
</Info>
|
||||
|
||||
## プラグインハンドラ
|
||||
|
||||
以下のコードがデフォルトで生成されます(`components/knowledge_engine/<エンジン名>.py`)。`ingest`、`retrieve`、`delete_document`の3つのコアメソッドを実装する必要があります。完全なコードは[langbot-plugin-demo](https://github.com/langbot-app/langbot-plugin-demo)の`SimpleKnowledgeEngine`で見つけることができます。
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.knowledge_engine.engine import KnowledgeEngine, KnowledgeEngineCapability
|
||||
from langbot_plugin.api.entities.builtin.rag.models import (
|
||||
IngestionContext,
|
||||
IngestionResult,
|
||||
)
|
||||
from langbot_plugin.api.entities.builtin.rag.context import (
|
||||
RetrievalContext,
|
||||
RetrievalResponse,
|
||||
RetrievalResultEntry,
|
||||
)
|
||||
from langbot_plugin.api.entities.builtin.rag.enums import DocumentStatus
|
||||
from langbot_plugin.api.entities.builtin.provider.message import ContentElement
|
||||
|
||||
|
||||
class SimpleRag(KnowledgeEngine):
|
||||
|
||||
@classmethod
|
||||
def get_capabilities(cls) -> list[str]:
|
||||
return [KnowledgeEngineCapability.DOC_INGESTION]
|
||||
|
||||
async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None:
|
||||
"""ナレッジベース作成時のコールバック、リソースの初期化に使用できます"""
|
||||
pass
|
||||
|
||||
async def on_knowledge_base_delete(self, kb_id: str) -> None:
|
||||
"""ナレッジベース削除時のコールバック、リソースのクリーンアップに使用できます"""
|
||||
pass
|
||||
|
||||
async def ingest(self, context: IngestionContext) -> IngestionResult:
|
||||
"""ドキュメントをナレッジベースに取り込む"""
|
||||
|
||||
# 1. ファイル内容を取得
|
||||
file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path)
|
||||
|
||||
# 2. ドキュメントを解析してチャンクに分割
|
||||
text = file_bytes.decode('utf-8')
|
||||
chunk_size = context.creation_settings.get('chunk_size', 500)
|
||||
chunk_overlap = context.creation_settings.get('chunk_overlap', 50)
|
||||
chunks = self._split_text(text, chunk_size, chunk_overlap)
|
||||
|
||||
# 3. ホストの埋め込みモデルを使用してベクトルを生成
|
||||
embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '')
|
||||
vectors = await self.plugin.invoke_embedding(embedding_model_uuid, chunks)
|
||||
|
||||
# 4. ホストのベクトルデータベースに書き込み
|
||||
collection_id = context.get_collection_id()
|
||||
ids = [f"{context.file_object.metadata.document_id}_{i}" for i in range(len(chunks))]
|
||||
metadata = [{"document_id": context.file_object.metadata.document_id, "chunk_index": i, "text": chunks[i]} for i in range(len(chunks))]
|
||||
await self.plugin.vector_upsert(collection_id, vectors, ids, metadata)
|
||||
|
||||
return IngestionResult(
|
||||
document_id=context.file_object.metadata.document_id,
|
||||
status=DocumentStatus.COMPLETED,
|
||||
chunks_created=len(chunks),
|
||||
)
|
||||
|
||||
async def retrieve(self, context: RetrievalContext) -> RetrievalResponse:
|
||||
"""ナレッジベースから関連コンテンツを検索"""
|
||||
|
||||
# 1. クエリベクトルを生成
|
||||
embedding_model_uuid = context.creation_settings.get('embedding_model_uuid', '')
|
||||
query_vectors = await self.plugin.invoke_embedding(embedding_model_uuid, [context.query])
|
||||
query_vector = query_vectors[0]
|
||||
|
||||
# 2. ベクトル検索
|
||||
collection_id = context.get_collection_id()
|
||||
results = await self.plugin.vector_search(collection_id, query_vector, top_k=context.retrieval_settings.get('top_k', 5))
|
||||
|
||||
# 3. 検索結果に変換
|
||||
entries = []
|
||||
for r in results:
|
||||
entry = RetrievalResultEntry(
|
||||
id=r.get('id', ''),
|
||||
content=[ContentElement.from_text(r.get('metadata', {}).get('text', ''))],
|
||||
metadata=r.get('metadata', {}),
|
||||
distance=r.get('score', 0.0),
|
||||
)
|
||||
entries.append(entry)
|
||||
|
||||
return RetrievalResponse(
|
||||
results=entries,
|
||||
total_found=len(entries),
|
||||
metadata={},
|
||||
)
|
||||
|
||||
async def delete_document(self, kb_id: str, document_id: str) -> bool:
|
||||
"""ナレッジベースからドキュメントと関連データを削除"""
|
||||
collection_id = kb_id
|
||||
deleted = await self.plugin.vector_delete(collection_id, file_ids=[document_id])
|
||||
return deleted > 0
|
||||
```
|
||||
|
||||
### ライフサイクルフック
|
||||
|
||||
KnowledgeEngineは、ナレッジベースの作成・削除時に呼び出される2つのライフサイクルフックを提供します:
|
||||
|
||||
```python
|
||||
async def on_knowledge_base_create(self, kb_id: str, config: dict) -> None:
|
||||
"""このエンジンを使用するナレッジベースが作成された時のコールバック
|
||||
|
||||
Args:
|
||||
kb_id: ナレッジベースUUID
|
||||
config: ユーザーが作成時に入力した設定(creation_schemaのフィールド)
|
||||
"""
|
||||
|
||||
async def on_knowledge_base_delete(self, kb_id: str) -> None:
|
||||
"""このエンジンを使用するナレッジベースが削除された時のコールバック
|
||||
|
||||
Args:
|
||||
kb_id: ナレッジベースUUID
|
||||
"""
|
||||
```
|
||||
|
||||
### ドキュメントの取り込み
|
||||
|
||||
`ingest`メソッドは、ユーザーがナレッジベースにドキュメントをアップロードした時に呼び出されます:
|
||||
|
||||
```python
|
||||
async def ingest(self, context: IngestionContext) -> IngestionResult:
|
||||
```
|
||||
|
||||
**IngestionContext**には以下の情報が含まれます:
|
||||
|
||||
```python
|
||||
class IngestionContext(pydantic.BaseModel):
|
||||
file_object: FileObject # 取り込むファイルオブジェクト
|
||||
knowledge_base_id: str # ターゲットのナレッジベースID
|
||||
collection_id: str | None # ベクトルコレクションID(デフォルトはknowledge_base_idにフォールバック)
|
||||
creation_settings: dict # ナレッジベース作成時の設定パラメータ
|
||||
parsed_content: ParseResult | None # 外部Parserプラグインの事前解析コンテンツ(存在する場合)
|
||||
```
|
||||
|
||||
**FileObject**にはファイルのメタ情報が含まれます:
|
||||
|
||||
```python
|
||||
class FileObject(pydantic.BaseModel):
|
||||
metadata: FileMetadata # filename, file_size, mime_type, document_id, knowledge_base_id等を含む
|
||||
storage_path: str # ストレージシステム内のファイルパス
|
||||
```
|
||||
|
||||
**IngestionResult**は取り込み結果を返す必要があります:
|
||||
|
||||
```python
|
||||
class IngestionResult(pydantic.BaseModel):
|
||||
document_id: str # ドキュメントID
|
||||
status: DocumentStatus # 処理ステータス: COMPLETED / FAILED
|
||||
chunks_created: int = 0 # 作成されたチャンク数
|
||||
error_message: str | None = None # 失敗時のエラーメッセージ
|
||||
metadata: dict = {} # 追加メタデータ
|
||||
```
|
||||
|
||||
### ナレッジ検索
|
||||
|
||||
`retrieve`メソッドは、ナレッジベースがクエリされた時に呼び出されます:
|
||||
|
||||
```python
|
||||
async def retrieve(self, context: RetrievalContext) -> RetrievalResponse:
|
||||
```
|
||||
|
||||
**RetrievalContext**には以下の情報が含まれます:
|
||||
|
||||
```python
|
||||
class RetrievalContext(pydantic.BaseModel):
|
||||
query: str # クエリテキスト
|
||||
knowledge_base_id: str | None # ナレッジベースID
|
||||
collection_id: str | None # ベクトルコレクションID
|
||||
retrieval_settings: dict # 検索時の設定パラメータ(retrieval_schemaのフィールド)
|
||||
creation_settings: dict # ナレッジベース作成時の設定パラメータ
|
||||
filters: dict # メタデータフィルター条件(Chromaスタイルwhere構文)
|
||||
```
|
||||
|
||||
<Info>
|
||||
LangBotホストは`retrieval_settings`をプラグインに渡す前にデフォルトの`top_k`値(デフォルト5)を自動的に注入します。プラグインは`context.retrieval_settings.get('top_k', 5)`でアクセスできます。
|
||||
|
||||
`filters`フィールドには`retrieval_settings`から抽出されたChromaスタイルの`where`フィルター条件が含まれます。呼び出し元が検索設定で`filters`を指定した場合(例:時間範囲、ファイルタイプ、カスタムメタデータフィールドによるドキュメントフィルタリング)、ホストがこのフィールドを設定し、プラグインは検索時にフィルタリングを適用できます。フィルターが指定されていない場合、このフィールドは空のdictです。
|
||||
</Info>
|
||||
|
||||
**RetrievalResponse**は検索結果を返す必要があります:
|
||||
|
||||
```python
|
||||
class RetrievalResponse(pydantic.BaseModel):
|
||||
results: list[RetrievalResultEntry] # 検索結果リスト
|
||||
total_found: int # 一致した総数
|
||||
metadata: dict # 追加メタデータ
|
||||
```
|
||||
|
||||
**RetrievalResultEntry**は単一の検索結果を表します:
|
||||
|
||||
```python
|
||||
class RetrievalResultEntry(pydantic.BaseModel):
|
||||
id: str # 結果ID
|
||||
content: list[ContentElement] # コンテンツ、ContentElement.from_text()を使用して作成
|
||||
metadata: dict # メタデータ
|
||||
distance: float # 距離スコア(小さいほど関連性が高い)
|
||||
score: float | None = None # 類似度スコア(大きいほど関連性が高い)
|
||||
```
|
||||
|
||||
### ドキュメントの削除
|
||||
|
||||
`delete_document`メソッドは、ユーザーがナレッジベースからドキュメントを削除した時に呼び出されます:
|
||||
|
||||
```python
|
||||
async def delete_document(self, kb_id: str, document_id: str) -> bool:
|
||||
"""ドキュメントと関連データを削除
|
||||
|
||||
Args:
|
||||
kb_id: ナレッジベースID
|
||||
document_id: ドキュメントID
|
||||
|
||||
Returns:
|
||||
削除が成功したかどうか
|
||||
"""
|
||||
```
|
||||
|
||||
## ホストRAG API
|
||||
|
||||
KnowledgeEngineコンポーネントは`self.plugin`を通じて、LangBotホストが提供する埋め込みモデル呼び出し、ベクトルデータベース操作、ファイル読み取りなどのRAG関連APIを呼び出すことができます。
|
||||
|
||||
### 埋め込みモデルの呼び出し
|
||||
|
||||
```python
|
||||
async def invoke_embedding(
|
||||
self,
|
||||
embedding_model_uuid: str,
|
||||
texts: list[str],
|
||||
) -> list[list[float]]:
|
||||
"""ホストの埋め込みモデルを使用してベクトルを生成
|
||||
|
||||
Args:
|
||||
embedding_model_uuid: 埋め込みモデルUUID
|
||||
texts: 埋め込むテキストのリスト
|
||||
|
||||
Returns:
|
||||
ベクトルのリスト、入力テキストごとに1つ
|
||||
"""
|
||||
|
||||
# 使用例
|
||||
vectors = await self.plugin.invoke_embedding("model_uuid", ["Hello", "World"])
|
||||
```
|
||||
|
||||
### ベクトルの書き込み
|
||||
|
||||
```python
|
||||
async def vector_upsert(
|
||||
self,
|
||||
collection_id: str,
|
||||
vectors: list[list[float]],
|
||||
ids: list[str],
|
||||
metadata: list[dict] | None = None,
|
||||
) -> None:
|
||||
"""ホストのベクトルデータベースにベクトルを書き込む
|
||||
|
||||
Args:
|
||||
collection_id: ターゲットコレクションID
|
||||
vectors: ベクトルのリスト
|
||||
ids: ベクトルの一意識別子リスト
|
||||
metadata: オプションのメタデータリスト
|
||||
"""
|
||||
|
||||
# 使用例
|
||||
await self.plugin.vector_upsert(
|
||||
collection_id="kb_uuid",
|
||||
vectors=[[0.1, 0.2, ...], [0.3, 0.4, ...]],
|
||||
ids=["chunk_0", "chunk_1"],
|
||||
metadata=[{"document_id": "doc1"}, {"document_id": "doc1"}],
|
||||
)
|
||||
```
|
||||
|
||||
### ベクトル検索
|
||||
|
||||
```python
|
||||
async def vector_search(
|
||||
self,
|
||||
collection_id: str,
|
||||
query_vector: list[float],
|
||||
top_k: int = 5,
|
||||
filters: dict | None = None,
|
||||
) -> list[dict]:
|
||||
"""ホストのベクトルデータベースで類似ベクトルを検索
|
||||
|
||||
Args:
|
||||
collection_id: ターゲットコレクションID
|
||||
query_vector: 類似性検索のクエリベクトル
|
||||
top_k: 返す結果の数
|
||||
filters: オプションのメタデータフィルター
|
||||
|
||||
Returns:
|
||||
検索結果のリスト(id, score, metadataなどを含むdict)
|
||||
"""
|
||||
|
||||
# 使用例
|
||||
results = await self.plugin.vector_search(
|
||||
collection_id="kb_uuid",
|
||||
query_vector=[0.1, 0.2, ...],
|
||||
top_k=5,
|
||||
)
|
||||
# 返却フォーマット: [{"id": "chunk_0", "score": 0.123, "metadata": {"document_id": "doc1", ...}}, ...]
|
||||
```
|
||||
|
||||
<Info>
|
||||
`vector_search`が返す各結果はdictで、`id`(ベクトルID)、`score`(距離スコア)、`metadata`(upsert時に提供したメタデータ)の3つのフィールドを含みます。検索結果にテキスト内容が必要な場合は、取り込み時にmetadataにテキストを保存してください。
|
||||
</Info>
|
||||
|
||||
### ベクトルの削除
|
||||
|
||||
```python
|
||||
async def vector_delete(
|
||||
self,
|
||||
collection_id: str,
|
||||
file_ids: list[str] | None = None,
|
||||
filters: dict | None = None,
|
||||
) -> int:
|
||||
"""ホストのベクトルデータベースからベクトルを削除
|
||||
|
||||
Args:
|
||||
collection_id: ターゲットコレクションID
|
||||
file_ids: 削除するファイルIDのリスト
|
||||
filters: オプションのメタデータフィルター
|
||||
|
||||
Returns:
|
||||
削除されたアイテム数
|
||||
"""
|
||||
|
||||
# 使用例
|
||||
deleted = await self.plugin.vector_delete(
|
||||
collection_id="kb_uuid",
|
||||
file_ids=["doc_001"],
|
||||
)
|
||||
```
|
||||
|
||||
<Info>
|
||||
`filters`パラメータはChromaスタイルの`where`構文によるメタデータフィルタリングをサポートしています。複数のトップレベルキーはAND条件として結合されます。サポートされる演算子:`$eq`、`$ne`、`$gt`、`$gte`、`$lt`、`$lte`、`$in`、`$nin`。例:`{"file_id": {"$eq": "abc"}}`。
|
||||
|
||||
**注意:** Chroma、Qdrant、SeekDBは完全なメタデータを保存し、任意のフィールドでフィルタリングできます。MilvusとpgvectorはDB側に`text`、`file_id`、`chunk_uuid`のみ保存しており、それ以外のフィールドでのフィルタリングは無視されます。
|
||||
</Info>
|
||||
|
||||
```python
|
||||
async def get_knowledge_file_stream(self, storage_path: str) -> bytes:
|
||||
"""ホストストレージからファイル内容を取得
|
||||
|
||||
Args:
|
||||
storage_path: ファイルのストレージパス(FileObject.storage_pathから取得)
|
||||
|
||||
Returns:
|
||||
ファイル内容のバイトデータ
|
||||
"""
|
||||
|
||||
# 使用例
|
||||
file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path)
|
||||
```
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
1 つの RAG エンジンは**複数のナレッジベース**から同時に使われ、プラットフォームのストレージキー `(instance, Workspace, owner type, plugin, key)` にはナレッジベースの次元がありません:
|
||||
|
||||
- 永続化とキャッシュのキーには**ナレッジベース ID** を含めてください。含めないと、別々のナレッジベースのベクトルやドキュメント状態が互いを上書きします。
|
||||
- ロックと「要対账」状態は `(installation, ナレッジベース)` 単位で記録してください。**1 つのナレッジベースの取り込み失敗が、同じ installation の他のナレッジベースの取り込みや検索を遮断してはいけません。**
|
||||
- 手動対账が必要な状態に入るのは、送出済みで結果不明の変更だけにしてください。決定的な検証エラーや送出前のキャンセルは後続の呼び出しに影響させないでください。
|
||||
- テレメトリや診断の失敗は業務に影響させないでください。ログや送信の失敗で取り込みや検索を中断せず、テナントデータをプロセス全体のシングルトンに書き込まないでください。
|
||||
- installation 単位でプロセス内キャッシュや状態を保持する場合は、`on_installation_revoked(binding)` で解放してください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## RAGエンジンのテスト
|
||||
|
||||
作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで:
|
||||
|
||||
1. 「ナレッジベース」ページに移動
|
||||
2. 「ナレッジベースを作成」をクリック
|
||||
3. プラグインが提供するRAGエンジンを選択し、エンジンの`creation_schema`に基づいて設定を入力
|
||||
4. 作成後、ドキュメントをアップロードして取り込み機能をテスト(エンジンが`DOC_INGESTION`能力を宣言している場合)
|
||||
5. パイプラインにナレッジベースをバインドし、検索機能をテスト
|
||||
@@ -0,0 +1,278 @@
|
||||
---
|
||||
title: "コンポーネント: ナレッジリトリーバー"
|
||||
---
|
||||
|
||||
ナレッジリトリーバーコンポーネントにより、プラグインはLangBotに外部ナレッジベース検索機能を提供できます。ユーザーがLangBotで外部ナレッジベースを作成する際、プラグインが提供するナレッジリトリーバーを選択してナレッジを検索できます。
|
||||
|
||||
<img width="600" src="/images/zh/plugin/dev/components/knowledge_retriever_use.png" />
|
||||
|
||||
## ナレッジリトリーバーコンポーネントの追加
|
||||
|
||||
1つのプラグインには任意の数のナレッジリトリーバーを追加できます。プラグインディレクトリでコマンド`lbp comp KnowledgeRetriever`を実行し、プロンプトに従ってナレッジリトリーバーの設定を入力します。
|
||||
|
||||
```bash
|
||||
➜ FastGPTRetriever > lbp comp KnowledgeRetriever
|
||||
Generating component KnowledgeRetriever...
|
||||
KnowledgeRetriever name: fastgpt
|
||||
KnowledgeRetriever description: Retrieve knowledge from FastGPT knowledge bases
|
||||
Component KnowledgeRetriever generated successfully.
|
||||
```
|
||||
|
||||
これにより、`components/knowledge_retriever/`ディレクトリに`fastgpt.yaml`と`fastgpt.py`ファイルが生成されます。`.yaml`ファイルはナレッジリトリーバーの基本情報と設定パラメータを定義し、`.py`ファイルはこのリトリーバーのハンドラです:
|
||||
|
||||
```bash
|
||||
➜ FastGPTRetriever > tree
|
||||
...
|
||||
├── components
|
||||
│ ├── __init__.py
|
||||
│ └── knowledge_retriever
|
||||
│ ├── __init__.py
|
||||
│ ├── fastgpt.py
|
||||
│ └── fastgpt.yaml
|
||||
...
|
||||
```
|
||||
|
||||
## マニフェストファイル: ナレッジリトリーバー
|
||||
|
||||
```yaml
|
||||
apiVersion: v1 # 変更しないでください
|
||||
kind: KnowledgeRetriever # 変更しないでください
|
||||
metadata:
|
||||
name: fastgpt # ナレッジリトリーバー名、このリトリーバーを識別するために使用
|
||||
label:
|
||||
en_US: FastGPT Knowledge Base # リトリーバー表示名、LangBotのUIに表示、多言語対応
|
||||
zh_Hans: FastGPT 知識庫
|
||||
ja_JP: FastGPT ナレッジベース
|
||||
description:
|
||||
en_US: 'Retrieve knowledge from FastGPT knowledge bases' # リトリーバー説明、LangBotのUIに表示、多言語対応。オプション
|
||||
zh_Hans: '従 FastGPT 知識庫中検索知識'
|
||||
ja_JP: 'FastGPT ナレッジベースから知識を取得'
|
||||
icon: assets/icon.svg # リトリーバーアイコン、外部ナレッジベース設定ページに表示
|
||||
spec:
|
||||
config: # リトリーバー設定パラメータ、ユーザーは外部ナレッジベース作成時にこれらのパラメータを入力する必要があります
|
||||
- name: api_base_url # パラメータ名
|
||||
type: string # パラメータタイプ: string、number、boolean、select
|
||||
label:
|
||||
en_US: API Base URL
|
||||
zh_Hans: API 基礎地址
|
||||
ja_JP: API ベース URL
|
||||
description:
|
||||
en_US: 'Base URL for FastGPT API'
|
||||
zh_Hans: 'FastGPT API 基礎地址'
|
||||
ja_JP: 'FastGPT API ベース URL'
|
||||
default: 'http://localhost:3000' # デフォルトパラメータ値
|
||||
required: true # 必須かどうか
|
||||
- name: api_key
|
||||
type: string
|
||||
label:
|
||||
en_US: FastGPT API Key
|
||||
zh_Hans: FastGPT API Key
|
||||
ja_JP: FastGPT API キー
|
||||
description:
|
||||
en_US: 'API key from your FastGPT instance'
|
||||
zh_Hans: '従您的 FastGPT 実例獲取的 API Key'
|
||||
ja_JP: 'FastGPT インスタンスから取得した API Key'
|
||||
default: ''
|
||||
required: true
|
||||
- name: search_mode # select型パラメータの例
|
||||
type: select
|
||||
label:
|
||||
en_US: Search Mode
|
||||
zh_Hans: 搜索模式
|
||||
ja_JP: 検索モード
|
||||
description:
|
||||
en_US: 'The search method to use'
|
||||
zh_Hans: '使用的搜索方法'
|
||||
ja_JP: '使用する検索方法'
|
||||
default: 'embedding'
|
||||
options: # select型パラメータはオプションリストを定義する必要があります
|
||||
- name: 'embedding'
|
||||
label:
|
||||
en_US: 'Embedding Search'
|
||||
zh_Hans: '向量搜索'
|
||||
ja_JP: '埋め込み検索'
|
||||
- name: 'fullTextRecall'
|
||||
label:
|
||||
en_US: 'Full-Text Recall'
|
||||
zh_Hans: '全文検索'
|
||||
ja_JP: '全文検索'
|
||||
- name: using_rerank # boolean型パラメータの例
|
||||
type: boolean
|
||||
label:
|
||||
en_US: Use Re-ranking
|
||||
zh_Hans: 使用重排序
|
||||
ja_JP: リランキングを使用
|
||||
description:
|
||||
en_US: 'Whether to use re-ranking'
|
||||
zh_Hans: '是否使用重排序'
|
||||
ja_JP: 'リランキングを使用するかどうか'
|
||||
default: false
|
||||
required: false
|
||||
execution:
|
||||
python:
|
||||
path: fastgpt.py # リトリーバーハンドラ、変更しないでください
|
||||
attr: FastGPT # リトリーバーハンドラのクラス名、fastgpt.pyのクラス名と一致
|
||||
```
|
||||
|
||||
設定項目フォーマットのリファレンスについては、[プラグインマニフェスト設定フォーマット](/en/plugin/dev/basic-info)を参照してください。
|
||||
|
||||
## プラグインハンドラ
|
||||
|
||||
以下のコードがデフォルトで生成されます(`components/knowledge_retriever/<retriever_name>.py`)。`FastGPT`クラスの`retrieve`メソッドでナレッジ検索ロジックを実装する必要があります。完全なコードは[langbot-plugin-demo](https://github.com/langbot-app/langbot-plugin-demo)で見つけることができます。
|
||||
|
||||
```python
|
||||
# Auto generated by LangBot Plugin SDK.
|
||||
# Please refer to https://langbot.app/docs/en/plugin/dev/tutor.html for more details.
|
||||
...
|
||||
|
||||
class FastGPT(KnowledgeRetriever):
|
||||
|
||||
async def retrieve(self, context: RetrievalContext) -> list[RetrievalResultEntry]:
|
||||
"""FastGPTナレッジベースからナレッジを検索"""
|
||||
|
||||
# 1. 設定パラメータを取得
|
||||
api_base_url = self.config.get('api_base_url', 'http://localhost:3000')
|
||||
api_key = self.config.get('api_key')
|
||||
dataset_id = self.config.get('dataset_id')
|
||||
search_mode = self.config.get('search_mode', 'embedding')
|
||||
using_rerank = self.config.get('using_rerank', False)
|
||||
|
||||
# 2. パラメータ検証
|
||||
if not api_key or not dataset_id:
|
||||
logger.error("Missing required configuration: api_key or dataset_id")
|
||||
return []
|
||||
|
||||
# 3. APIリクエストを構築
|
||||
url = f"{api_base_url.rstrip('/')}/api/core/dataset/searchTest"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
payload = {
|
||||
"datasetId": dataset_id,
|
||||
"text": context.query, # RetrievalContextからクエリテキストを取得
|
||||
"searchMode": search_mode,
|
||||
"usingReRank": using_rerank,
|
||||
}
|
||||
|
||||
try:
|
||||
# 4. 外部APIを呼び出す
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(url, json=payload, headers=headers, timeout=30.0)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
# 5. レスポンスを解析してRetrievalResultEntryに変換
|
||||
results = []
|
||||
for record in result.get('data', []):
|
||||
# メインデータと補助データをコンテンツとして結合
|
||||
content_text = '\n'.join([
|
||||
record.get('q', ''),
|
||||
record.get('a', '')
|
||||
]).strip()
|
||||
|
||||
# 検索結果エントリを作成
|
||||
entry = RetrievalResultEntry(
|
||||
id=record.get('id', ''),
|
||||
content=[ContentElement.from_text(content_text)],
|
||||
metadata={
|
||||
'dataset_id': record.get('datasetId', ''),
|
||||
'source_name': record.get('sourceName', ''),
|
||||
'score': record.get('score', 0.0),
|
||||
},
|
||||
# 類似度スコアを距離に変換(スコアが高いほど距離が小さい)
|
||||
distance=1.0 - float(record.get('score', 0.0)),
|
||||
)
|
||||
results.append(entry)
|
||||
|
||||
logger.info(f"Retrieved {len(results)} chunks from FastGPT dataset {dataset_id}")
|
||||
return results
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.error(f"HTTP error from FastGPT API: {e.response.status_code} - {e.response.text}")
|
||||
return []
|
||||
except httpx.RequestError as e:
|
||||
logger.error(f"Request error when calling FastGPT API: {str(e)}")
|
||||
return []
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
logger.error(f"Unexpected error during retrieval: {str(e)}")
|
||||
return []
|
||||
```
|
||||
|
||||
### 検索コンテキスト
|
||||
|
||||
`RetrievalContext`にはこの検索のコンテキスト情報が含まれます:
|
||||
|
||||
```python
|
||||
class RetrievalContext(pydantic.BaseModel):
|
||||
"""ナレッジ検索コンテキスト"""
|
||||
|
||||
query: str
|
||||
"""クエリテキスト、ユーザーの検索質問"""
|
||||
```
|
||||
|
||||
### 検索結果
|
||||
|
||||
検索結果は`RetrievalResultEntry`オブジェクトのリストに変換する必要があり、各オブジェクトは検索されたナレッジチャンクを表します:
|
||||
|
||||
```python
|
||||
class RetrievalResultEntry(pydantic.BaseModel):
|
||||
"""単一検索結果エントリ"""
|
||||
|
||||
id: str
|
||||
"""結果ID、このエントリを一意に識別"""
|
||||
|
||||
content: list[ContentElement]
|
||||
"""結果コンテンツ、ContentElement.from_text()を使用してテキストコンテンツを作成"""
|
||||
|
||||
metadata: dict[str, Any]
|
||||
"""結果メタデータ、ソース、スコアなど、任意のキーと値のペアを含めることができます"""
|
||||
|
||||
distance: float
|
||||
"""距離スコア、クエリとの関連性を示します(小さいほど関連性が高い)
|
||||
通常は1.0 - similarity_scoreとして計算されます"""
|
||||
```
|
||||
|
||||
### 設定パラメータの取得
|
||||
|
||||
`self.config.get(パラメータ名, デフォルト値)`を使用して、ユーザーが設定したパラメータ値を取得します:
|
||||
|
||||
```python
|
||||
# 文字列パラメータを取得
|
||||
api_base_url = self.config.get('api_base_url', 'http://localhost:3000')
|
||||
|
||||
# 数値パラメータを取得
|
||||
limit = self.config.get('limit', 5000)
|
||||
similarity = self.config.get('similarity', 0.0)
|
||||
|
||||
# ブールパラメータを取得
|
||||
using_rerank = self.config.get('using_rerank', False)
|
||||
|
||||
# selectパラメータを取得
|
||||
search_mode = self.config.get('search_mode', 'embedding')
|
||||
```
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
1 つのナレッジリトリーバーは**複数のナレッジベース**から使われ、プラットフォームのストレージキー `(instance, Workspace, owner type, plugin, key)` にはナレッジベースの次元がありません:
|
||||
|
||||
- 検索に使うキャッシュ、インデックス、中間結果は**ナレッジベース ID** で分離し、キーにナレッジベース ID を含めてください。インスタンスに置かないでください。
|
||||
- 検索のロックと「要対账」状態は `(installation, ナレッジベース)` 単位で記録してください。あるナレッジベースの検索失敗が、同じ installation の他のナレッジベースの検索を利用不能にしてはいけません。
|
||||
- `RetrievalContext` は 1 回の検索でのみ有効です。インスタンスフィールドに保存して呼び出しをまたいで再利用しないでください。
|
||||
- 呼び出しをまたいで保持するナレッジベースのデータは Host ストレージに置き、キーに installation の識別子とナレッジベース ID の両方を含めてください。
|
||||
- installation 単位で接続、インデックス、その他のプロセス内状態をキャッシュする場合は、`on_installation_revoked(binding)` で解放してください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## リトリーバーのテスト
|
||||
|
||||
作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで:
|
||||
|
||||
1. 「ナレッジベース」ページに移動
|
||||
2. 外部ナレッジベースを追加
|
||||
3. プラグインが提供するナレッジリトリーバーを選択し、設定を入力
|
||||
|
||||
<img width="600" src="/images/zh/plugin/dev/components/knowledge_retriever_config.png" />
|
||||
|
||||
保存後、LangBotパイプラインでこのナレッジベースを選択できます。
|
||||
@@ -0,0 +1,255 @@
|
||||
---
|
||||
title: "コンポーネント:ページ"
|
||||
---
|
||||
|
||||
Pageコンポーネントを使用すると、プラグインはLangBot WebUIのサイドバーにカスタムビジュアルページを登録できます。ページはiframeサンドボックス内で実行され、Page SDKを介してホストと通信し、プラグインのバックエンドAPIを呼び出すことができます。
|
||||
|
||||
## ユースケース
|
||||
|
||||
- **管理パネル**:プラグインに視覚的な設定やデータ管理インターフェースを提供(例:FAQ管理、分析ダッシュボード)
|
||||
- **データ表示**:ランタイムの統計データ、ログ、チャートなどを表示
|
||||
- **インタラクティブツール**:フォーム、エディタなどの対話式インターフェースを提供
|
||||
|
||||
<img width="600" src="/images/zh/plugin/dev/components/plugin-page-demo.png" />
|
||||
|
||||
## ページコンポーネントの追加
|
||||
|
||||
プラグインディレクトリでコマンドを実行します:
|
||||
|
||||
```bash
|
||||
lbp comp Page
|
||||
```
|
||||
|
||||
ページ名を入力すると、`components/pages/`ディレクトリにページファイルが生成されます:
|
||||
|
||||
```bash
|
||||
├── components
|
||||
│ └── pages
|
||||
│ └── dashboard
|
||||
│ ├── dashboard.yaml # ページマニフェスト
|
||||
│ ├── dashboard.py # バックエンドハンドラー
|
||||
│ ├── index.html # ページエントリポイント
|
||||
│ └── i18n # 翻訳ファイル(オプション)
|
||||
│ ├── en_US.json
|
||||
│ └── ja_JP.json
|
||||
```
|
||||
|
||||
`manifest.yaml`にもコンポーネント検出設定が追加されます:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
components:
|
||||
Page:
|
||||
fromDirs:
|
||||
- path: components/pages/
|
||||
maxDepth: 2
|
||||
```
|
||||
|
||||
## マニフェストファイル:ページ
|
||||
|
||||
```yaml
|
||||
apiVersion: v1 # 変更しないでください
|
||||
kind: Page # 変更しないでください
|
||||
metadata:
|
||||
name: dashboard # プラグイン内で一意のページID
|
||||
label:
|
||||
en_US: Dashboard # WebUIサイドバーに表示される名前、多言語対応
|
||||
ja_JP: ダッシュボード
|
||||
spec:
|
||||
path: index.html # HTMLエントリファイル(このYAMLファイルからの相対パス)
|
||||
execution:
|
||||
python:
|
||||
path: dashboard.py # バックエンドハンドラーファイル
|
||||
attr: DashboardPage # ハンドラークラス名
|
||||
```
|
||||
|
||||
## バックエンド処理
|
||||
|
||||
Pageコンポーネントのバックエンドハンドラーは`Page`基底クラスを継承し、`handle_api`メソッドを実装してフロントエンドページからのAPIリクエストを処理します。
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.page import Page, PageRequest, PageResponse
|
||||
|
||||
|
||||
class DashboardPage(Page):
|
||||
|
||||
async def handle_api(self, request: PageRequest) -> PageResponse:
|
||||
# request.endpoint: APIエンドポイントパス(例:'/stats')
|
||||
# request.method: HTTPメソッド(GET、POST、PUT、DELETE)
|
||||
# request.body: リクエストボディ(解析済みJSON、またはNone)
|
||||
|
||||
if request.endpoint == '/stats' and request.method == 'GET':
|
||||
# Host storage から現在の installation のデータを読む
|
||||
entries = await self.load_entries_for_current_installation()
|
||||
return PageResponse.ok({'total': len(entries)})
|
||||
|
||||
return PageResponse.fail(f'不明なエンドポイント: {request.endpoint}')
|
||||
```
|
||||
|
||||
### PageRequestフィールド
|
||||
|
||||
| フィールド | 型 | 説明 |
|
||||
|-----------|------|------|
|
||||
| `endpoint` | `str` | APIエンドポイントパス(例:`'/entries'`) |
|
||||
| `method` | `str` | HTTPメソッド(`GET`、`POST`、`PUT`、`DELETE`) |
|
||||
| `body` | `Any` | リクエストボディ(解析済みJSON、または`None`) |
|
||||
|
||||
### PageResponseの構築
|
||||
|
||||
| メソッド | 説明 |
|
||||
|---------|------|
|
||||
| `PageResponse.ok(data)` | 成功レスポンス。`data`はJSON変換可能な任意の値 |
|
||||
| `PageResponse.fail(error)` | エラーレスポンス。`error`はエラーメッセージ文字列 |
|
||||
|
||||
## フロントエンドページ開発
|
||||
|
||||
HTMLファイルにPage SDKを読み込んで、プラグインバックエンドと通信します:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {
|
||||
background: var(--langbot-bg, #ffffff);
|
||||
color: var(--langbot-text, #0a0a0a);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 data-i18n="title">ダッシュボード</h1>
|
||||
<p id="stats"></p>
|
||||
|
||||
<script src="/api/v1/plugins/_sdk/page-sdk.js"></script>
|
||||
<script>
|
||||
langbot.onReady(async function(ctx) {
|
||||
// バックエンドAPIを呼び出す
|
||||
var data = await langbot.api('/stats', null, 'GET');
|
||||
document.getElementById('stats').textContent = '合計: ' + data.total;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Page SDK API
|
||||
|
||||
| メソッド | 説明 |
|
||||
|---------|------|
|
||||
| `langbot.onReady(callback)` | SDK準備完了時に呼ばれる。`callback`は`ctx`(`theme`と`language`を含む)を受け取る |
|
||||
| `langbot.api(endpoint, body?, method?)` | プラグインの`handle_api`を呼び出す。Promiseを返す |
|
||||
| `langbot.t(key, fallback?)` | 翻訳文字列を取得 |
|
||||
| `langbot.onThemeChange(callback)` | テーマ変更時のコールバック |
|
||||
| `langbot.onLanguageChange(callback)` | 言語変更時のコールバック |
|
||||
| `langbot.applyI18n()` | `data-i18n`翻訳を手動で再適用 |
|
||||
|
||||
### ダークモード
|
||||
|
||||
SDKはページにCSSカスタムプロパティを自動設定します。直接使用できます:
|
||||
|
||||
| CSS変数 | 用途 |
|
||||
|---------|------|
|
||||
| `--langbot-bg` | ページ背景色 |
|
||||
| `--langbot-bg-card` | カード背景色 |
|
||||
| `--langbot-text` | メインテキスト色 |
|
||||
| `--langbot-text-muted` | サブテキスト色 |
|
||||
| `--langbot-border` | ボーダー色 |
|
||||
| `--langbot-accent` | アクセント色 |
|
||||
|
||||
### ページi18n
|
||||
|
||||
ページディレクトリ内に`i18n/`ディレクトリを作成し、JSON翻訳ファイルを配置します:
|
||||
|
||||
```
|
||||
pages/dashboard/
|
||||
├── index.html
|
||||
└── i18n/
|
||||
├── en_US.json
|
||||
└── ja_JP.json
|
||||
```
|
||||
|
||||
翻訳ファイルはフラットなJSONキーバリューペアです:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "ダッシュボード",
|
||||
"totalEntries": "エントリ数"
|
||||
}
|
||||
```
|
||||
|
||||
HTML要素に`data-i18n`属性を追加すると、SDKが自動的にテキストを翻訳します:
|
||||
|
||||
```html
|
||||
<h1 data-i18n="title">Dashboard</h1>
|
||||
```
|
||||
|
||||
## 完全な例:FAQ管理ページ
|
||||
|
||||
FAQエントリのCRUD操作を実装した完全なPageコンポーネントの例です。
|
||||
|
||||
**バックエンドハンドラー**(`components/pages/manager/manager.py`):
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.page import Page, PageRequest, PageResponse
|
||||
|
||||
|
||||
class ManagerPage(Page):
|
||||
|
||||
async def handle_api(self, request: PageRequest) -> PageResponse:
|
||||
if request.endpoint == '/entries' and request.method == 'GET':
|
||||
entries = await self.load_entries_for_current_installation()
|
||||
return PageResponse.ok({'entries': entries})
|
||||
|
||||
if request.endpoint == '/entries' and request.method == 'POST':
|
||||
question = (request.body or {}).get('question', '').strip()
|
||||
answer = (request.body or {}).get('answer', '').strip()
|
||||
if not question or not answer:
|
||||
return PageResponse.fail('質問と回答は必須です')
|
||||
entry = {'question': question, 'answer': answer}
|
||||
await self.save_entry_for_current_installation(entry)
|
||||
return PageResponse.ok({'entry': entry})
|
||||
|
||||
if request.endpoint == '/entries' and request.method == 'DELETE':
|
||||
entry_id = (request.body or {}).get('id', '')
|
||||
if await self.delete_entry_for_current_installation(entry_id):
|
||||
return PageResponse.ok({'deleted': entry_id})
|
||||
return PageResponse.fail('エントリが見つかりません')
|
||||
|
||||
return PageResponse.fail(f'不明: {request.method} {request.endpoint}')
|
||||
```
|
||||
|
||||
**フロントエンドページ**(`components/pages/manager/index.html`)では`langbot.api()`でバックエンドを呼び出します:
|
||||
|
||||
```javascript
|
||||
// エントリを読み込む
|
||||
var data = await langbot.api('/entries', null, 'GET');
|
||||
|
||||
// エントリを追加
|
||||
await langbot.api('/entries', { question: '...', answer: '...' }, 'POST');
|
||||
|
||||
// エントリを削除
|
||||
await langbot.api('/entries', { id: '...' }, 'DELETE');
|
||||
```
|
||||
|
||||
完全なサンプルコードは[FAQManagerプラグイン](https://github.com/langbot-app/langbot-plugin-demo/tree/main/FAQManager)を参照してください。
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
同じ Page は**複数のページリクエスト**から呼び出されます(同じ installation の別々のリクエスト、別々の Workspace):
|
||||
|
||||
- 前回のリクエストのデータをインスタンスに残さないでください。エンドポイント、メソッド、body、現在の installation の情報は、毎回現在の `PageRequest` / binding から取得します。
|
||||
- ページのデータは常に installation スコープの Host ストレージに置いてください。`self.plugin` はプロセスレベルの共有オブジェクトであり、テナントデータを保存できません。
|
||||
- 別々の Workspace のリクエストは同じ Page インスタンスを共有するため、インスタンスフィールドの使用が交錯します。リクエストをまたいで保持するデータは Host ストレージに置いてください。
|
||||
- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## 注意事項
|
||||
|
||||
- ページは`sandbox="allow-scripts allow-forms"`のiframe内で実行され、ポップアップを開いたり親ページをナビゲートすることはできません
|
||||
- 認証 Page のテナントデータは installation-scoped Host storage に保存します。`self.plugin` はプロセス全体で共有されるため、テナントのエントリを保持してはいけません。上記 helper 名は説明用です。現在の SDK Host storage API を使用してください。
|
||||
- `PageResponse.ok()`と`PageResponse.fail()`を使用してレスポンスを構築し、一貫したフォーマットを確保してください
|
||||
- Page SDKの`<script>`タグは、`langbot`オブジェクトを使用するコードの前に配置する必要があります
|
||||
@@ -0,0 +1,222 @@
|
||||
---
|
||||
title: "コンポーネント: パーサー"
|
||||
---
|
||||
|
||||
パーサー(Parser)コンポーネントにより、プラグインはLangBotにドキュメント解析機能を提供できます。ユーザーがナレッジベースにドキュメントをアップロードする際、LangBotはRAGエンジンの`ingest`ステップの前にパーサーを呼び出し、PDF、Word、Markdownなどのバイナリファイルから構造化テキストを抽出します。
|
||||
|
||||
ParserとKnowledgeEngineの関係:
|
||||
- **Parser** はファイルをテキストに変換する役割(file → text)
|
||||
- **KnowledgeEngine** はテキストのインデックス作成と検索を行う役割(text → chunks → vectors)
|
||||
|
||||
RAGエンジン自体がドキュメント解析機能を持っている場合(`DOC_PARSING`能力を宣言)、ユーザーはRAGエンジンの内蔵解析機能を使用するか、外部Parserプラグインを使用するかを選択できます。
|
||||
|
||||
## パーサーコンポーネントの追加
|
||||
|
||||
1つのプラグインには任意の数のパーサーを追加できます。プラグインディレクトリでコマンド`lbp comp Parser`を実行し、プロンプトに従ってパーサーの設定を入力します。
|
||||
|
||||
```bash
|
||||
➜ MyParserPlugin > lbp comp Parser
|
||||
Generating component Parser...
|
||||
Parser name: pdf_parser
|
||||
Parser description: A PDF document parser
|
||||
Component Parser generated successfully.
|
||||
組件 Parser 生成成功。
|
||||
```
|
||||
|
||||
これにより、`components/parser/`ディレクトリに`pdf_parser.yaml`と`pdf_parser.py`ファイルが生成されます。`.yaml`ファイルはパーサーの基本情報とサポートするMIMEタイプを定義し、`.py`ファイルはこのパーサーのハンドラです:
|
||||
|
||||
```bash
|
||||
➜ MyParserPlugin > tree
|
||||
...
|
||||
├── components
|
||||
│ ├── __init__.py
|
||||
│ └── parser
|
||||
│ ├── __init__.py
|
||||
│ ├── pdf_parser.py
|
||||
│ └── pdf_parser.yaml
|
||||
...
|
||||
```
|
||||
|
||||
## マニフェストファイル: パーサー
|
||||
|
||||
```yaml
|
||||
apiVersion: v1 # 変更しないでください
|
||||
kind: Parser # 変更しないでください
|
||||
metadata:
|
||||
name: pdf_parser # パーサー名、このパーサーを識別するために使用
|
||||
label:
|
||||
en_US: PDF Parser # パーサー表示名、LangBotのUIに表示、多言語対応
|
||||
zh_Hans: PDF 解析器
|
||||
description:
|
||||
en_US: 'A PDF document parser'
|
||||
zh_Hans: 'PDF 文档解析器'
|
||||
spec:
|
||||
supported_mime_types: # サポートするファイルのMIMEタイプを宣言
|
||||
- application/pdf
|
||||
execution:
|
||||
python:
|
||||
path: pdf_parser.py # パーサーハンドラ、変更しないでください
|
||||
attr: PdfParser # パーサーハンドラのクラス名、pdf_parser.pyのクラス名と一致
|
||||
```
|
||||
|
||||
### supported_mime_types
|
||||
|
||||
`supported_mime_types`はこのパーサーがサポートするファイルタイプを宣言します。一般的なMIMEタイプ:
|
||||
|
||||
| MIMEタイプ | 説明 |
|
||||
|----------|------|
|
||||
| `application/pdf` | PDFドキュメント |
|
||||
| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | Wordドキュメント(.docx) |
|
||||
| `text/markdown` | Markdownファイル |
|
||||
| `text/plain` | プレーンテキストファイル |
|
||||
| `text/html` | HTMLファイル |
|
||||
|
||||
## プラグインハンドラ
|
||||
|
||||
以下のコードがデフォルトで生成されます(`components/parser/<パーサー名>.py`)。`parse`メソッドを実装する必要があります。
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.parser.parser import Parser
|
||||
from langbot_plugin.api.entities.builtin.rag.models import (
|
||||
ParseContext,
|
||||
ParseResult,
|
||||
TextSection,
|
||||
)
|
||||
|
||||
|
||||
class PdfParser(Parser):
|
||||
"""ファイルからテキストを抽出するパーサーコンポーネント。"""
|
||||
|
||||
async def parse(self, context: ParseContext) -> ParseResult:
|
||||
"""ファイルを解析して構造化テキストを抽出。
|
||||
|
||||
Args:
|
||||
context: file_content(bytes)、mime_type、filename、metadataを含む。
|
||||
|
||||
Returns:
|
||||
抽出されたテキストとオプションの構造化セクションを含むParseResult。
|
||||
"""
|
||||
# TODO: 解析ロジックを実装
|
||||
text = context.file_content.decode("utf-8", errors="replace")
|
||||
|
||||
return ParseResult(
|
||||
text=text,
|
||||
sections=[
|
||||
TextSection(
|
||||
content=text,
|
||||
heading=context.filename,
|
||||
level=0,
|
||||
),
|
||||
],
|
||||
metadata={
|
||||
"filename": context.filename,
|
||||
"mime_type": context.mime_type,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 解析メソッド
|
||||
|
||||
`parse`メソッドは、ドキュメントがナレッジベースにアップロードされた時(RAGエンジンの取り込み前)に呼び出されます:
|
||||
|
||||
```python
|
||||
async def parse(self, context: ParseContext) -> ParseResult:
|
||||
```
|
||||
|
||||
**ParseContext**には以下の情報が含まれます:
|
||||
|
||||
```python
|
||||
class ParseContext(pydantic.BaseModel):
|
||||
file_content: bytes # 生のファイルバイト(LangBotがストレージから読み取り)
|
||||
mime_type: str # 検出されたファイルのMIMEタイプ
|
||||
filename: str # 元のファイル名
|
||||
metadata: dict[str, Any] # FileObjectからの追加メタデータ
|
||||
```
|
||||
|
||||
**ParseResult**は解析結果を返す必要があります:
|
||||
|
||||
```python
|
||||
class ParseResult(pydantic.BaseModel):
|
||||
text: str # 抽出された完全なプレーンテキスト
|
||||
sections: list[TextSection] = [] # 構造化セクション(オプション)
|
||||
metadata: dict[str, Any] = {} # 解析メタデータ(例: page_count, language)
|
||||
```
|
||||
|
||||
**TextSection**はドキュメントから抽出されたテキストのセクションを表します:
|
||||
|
||||
```python
|
||||
class TextSection(pydantic.BaseModel):
|
||||
content: str # セクションのテキスト内容
|
||||
heading: str | None = None # セクション見出し
|
||||
level: int = 0 # ネストレベル
|
||||
page: int | None = None # ソースページ番号(PDFなど)
|
||||
metadata: dict[str, Any] = {} # 追加セクションメタデータ
|
||||
```
|
||||
|
||||
## KnowledgeEngineとの連携
|
||||
|
||||
ユーザーがドキュメントをアップロードする際、LangBotは以下のロジックで解析フローを決定します:
|
||||
|
||||
- ユーザーが外部Parserプラグインを選択した場合、LangBotはまずParserの`parse`メソッドを呼び出し、その結果を`IngestionContext.parsed_content`を通じてRAGエンジンの`ingest`メソッドに渡します。
|
||||
- RAGエンジンが`DOC_PARSING`能力を宣言しており、ユーザーが外部パーサーを選択していない場合、RAGエンジンが自身でドキュメント解析を処理します。
|
||||
|
||||
KnowledgeEngineは`IngestionContext.parsed_content`をチェックして、事前解析されたコンテンツが利用可能かどうかを判断できます:
|
||||
|
||||
```python
|
||||
async def ingest(self, context: IngestionContext) -> IngestionResult:
|
||||
if context.parsed_content:
|
||||
# 外部Parserの解析結果を使用
|
||||
text = context.parsed_content.text
|
||||
sections = context.parsed_content.sections
|
||||
else:
|
||||
# ドキュメントを内部で解析
|
||||
file_bytes = await self.plugin.get_knowledge_file_stream(context.file_object.storage_path)
|
||||
text = file_bytes.decode('utf-8')
|
||||
...
|
||||
```
|
||||
|
||||
## クロスプラグインパーサー呼び出し
|
||||
|
||||
他のプラグインが提供するパーサーを呼び出す前に、`self.plugin.list_parsers` を使ってホスト上で現在利用可能なパーサーを列挙できます:
|
||||
|
||||
```python
|
||||
parsers = await self.plugin.list_parsers(mime_type="application/pdf")
|
||||
# 各要素には plugin_id、plugin_author、plugin_name、name、description、supported_mime_types が含まれます
|
||||
```
|
||||
|
||||
空配列が返る場合、その MIME タイプに対応する接続済みの Parser プラグインは現在存在しません。
|
||||
|
||||
`plugin_author` と `plugin_name` を取得したら、`self.plugin.invoke_parser` で対象のパーサーを呼び出せます:
|
||||
|
||||
```python
|
||||
parser = parsers[0]
|
||||
result = await self.plugin.invoke_parser(
|
||||
plugin_author=parser["plugin_author"],
|
||||
plugin_name=parser["plugin_name"],
|
||||
storage_path=context.file_object.storage_path,
|
||||
mime_type=context.file_object.metadata.mime_type,
|
||||
filename=context.file_object.metadata.filename,
|
||||
metadata={},
|
||||
)
|
||||
# resultはtext、sections、metadataを含むdict
|
||||
```
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
1 つの Parser は**複数のナレッジベース**から使われます(同じ installation の別々のナレッジベースが異なる MIME タイプのファイルをアップロードします)。プラットフォームのストレージキーにはナレッジベースの次元がありません:
|
||||
|
||||
- 解析キャッシュと一時ファイルは**ナレッジベース ID** で分離し、キーにナレッジベース ID を含めてください。インスタンスにキャッシュしないでください。
|
||||
- `ParseContext` と解析の中間結果は 1 回の解析でのみ有効です。インスタンスフィールドやモジュールレベルの変数に保存しないでください。
|
||||
- 解析の失敗が影響するのは現在のナレッジベースのそのアップロードだけです。他のナレッジベースの解析を利用不能にしてはいけません。
|
||||
- installation 単位でモデル、パーサー、一時ディレクトリをキャッシュする場合は、`on_installation_revoked(binding)` で解放し、一時ファイルを確実に削除できるようにしてください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## パーサーのテスト
|
||||
|
||||
作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで:
|
||||
|
||||
1. 「ナレッジベース」ページに移動
|
||||
2. ナレッジベースを選択してドキュメント管理に入る
|
||||
3. ファイルのアップロード時、パーサーセレクターでプラグインが提供するパーサーを選択
|
||||
4. アップロード後、ドキュメントが正しく取り込まれたことを確認
|
||||
@@ -0,0 +1,211 @@
|
||||
---
|
||||
title: "コンポーネント: Runner"
|
||||
---
|
||||
|
||||
Runner は Agent の実行ロジック、または「プラグインプロセッサー」用のイベント処理関数を実装するコンポーネントです。どちらも `RunnerContext` を受け取り、インストール後にユーザーがコンポーネントを選択・設定します。
|
||||
|
||||
環境の準備は[プラグイン開発チュートリアル](/ja/plugin/dev/tutor)を参照してください。
|
||||
|
||||
`RunnerContext` の完全なフィールドとメソッドは[コンテキスト API](/ja/plugin/dev/apis/agent-run#runner)を参照してください。
|
||||
|
||||
## Runner コンポーネントの追加
|
||||
|
||||
1 つのプラグインに複数のイベントプロセッサーを追加できます。プラグインのディレクトリで `lbp comp Runner` を実行し、名前に `welcome`、説明に `Welcome new members` を入力します。
|
||||
|
||||
```bash
|
||||
lbp comp Runner
|
||||
```
|
||||
|
||||
生成される `components/runner/welcome.yaml` はコンポーネントを定義するファイルで、`welcome.py` に処理を記述します。
|
||||
|
||||
## 実行ロジックの実装
|
||||
|
||||
モデルの接続や独自の実行ループには `spec.usages: [agent]` を宣言し、`run(ctx)` から結果を返します:
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.runner import Runner, RunnerContext, RunnerResult
|
||||
from langbot_plugin.api.entities.builtin.provider.message import Message
|
||||
|
||||
|
||||
class Echo(Runner):
|
||||
async def run(self, ctx: RunnerContext):
|
||||
yield RunnerResult.run_completed(
|
||||
ctx.run_id,
|
||||
message=Message(role="assistant", content=ctx.input.to_text()),
|
||||
)
|
||||
```
|
||||
|
||||
## マニフェストファイル: Runner
|
||||
|
||||
`welcome.yaml` を次の内容に変更します。
|
||||
|
||||
```yaml
|
||||
apiVersion: langbot/v1
|
||||
kind: Runner
|
||||
metadata:
|
||||
name: welcome
|
||||
label:
|
||||
en_US: Welcome members
|
||||
zh_Hans: 欢迎新成员
|
||||
ja_JP: 新しいメンバーを歓迎
|
||||
description:
|
||||
en_US: Welcome new members and respond to /hello.
|
||||
zh_Hans: 欢迎新成员,并响应 /hello 消息。
|
||||
ja_JP: 新しいメンバーを歓迎し、/hello に返信します。
|
||||
spec:
|
||||
usages: [event]
|
||||
events:
|
||||
- group.member_joined
|
||||
- message.received
|
||||
capabilities:
|
||||
tool_calling: true
|
||||
permissions:
|
||||
tools: [detail, call]
|
||||
config:
|
||||
- name: greeting
|
||||
type: string
|
||||
label:
|
||||
en_US: Greeting
|
||||
zh_Hans: 欢迎语
|
||||
ja_JP: 歓迎メッセージ
|
||||
required: true
|
||||
default: Welcome aboard!
|
||||
- name: reply_enabled
|
||||
type: boolean
|
||||
label:
|
||||
en_US: Send replies
|
||||
zh_Hans: 发送回复
|
||||
ja_JP: 返信する
|
||||
default: true
|
||||
execution:
|
||||
python:
|
||||
path: ./welcome.py
|
||||
attr: Welcome
|
||||
```
|
||||
|
||||
`spec.events` は対応するイベント、`spec.config` は詳細画面でユーザーが変更できる設定です。この例ではメンバー参加とメッセージ受信に対応し、歓迎メッセージと返信の有効・無効を設定できます。返信には、上記の `tool_calling` と `permissions.tools` の指定が必要です。
|
||||
|
||||
|
||||
`spec.usages` は必須で、デフォルト値はありません。`spec.usages` は選択先を指定します。`agent` は Agent とパイプライン、`event` はプラグインプロセッサーです。`[agent, event]` の併記も可能です。`event` には `spec.events` が必要です。以下はイベント処理関数の例です。独自の `run(ctx)` から `await super().run(ctx)` で呼び出すこともできます。
|
||||
|
||||
## プラグインの実装
|
||||
|
||||
`Welcome` クラスの `initialize` メソッド内で処理関数を登録します。`welcome.py` を次の内容に変更してください。
|
||||
|
||||
```python
|
||||
from langbot_plugin.api.definition.components.runner import (
|
||||
Runner,
|
||||
RunnerContext,
|
||||
)
|
||||
from langbot_plugin.api.entities.builtin.platform.events import (
|
||||
MemberJoinedEvent,
|
||||
MessageReceivedEvent,
|
||||
)
|
||||
from langbot_plugin.api.entities.builtin.platform.message import Plain
|
||||
|
||||
|
||||
class Welcome(Runner):
|
||||
async def initialize(self):
|
||||
await super().initialize()
|
||||
|
||||
@self.handler(MemberJoinedEvent)
|
||||
async def on_join(ctx: RunnerContext):
|
||||
name = ctx.platform_event.member.nickname or str(ctx.platform_event.member.id)
|
||||
await ctx.log(f"Member joined: {name}")
|
||||
if not ctx.config.get("reply_enabled", True):
|
||||
await ctx.log("Replies are disabled for this processor")
|
||||
return
|
||||
greeting = ctx.config.get("greeting", "Welcome aboard!")
|
||||
await ctx.reply(f"{name}, {greeting}")
|
||||
await ctx.log("Welcome action completed")
|
||||
|
||||
@self.handler(MessageReceivedEvent)
|
||||
async def on_message(ctx: RunnerContext):
|
||||
text = "".join(
|
||||
item.text for item in ctx.platform_event.message_chain if isinstance(item, Plain)
|
||||
).strip()
|
||||
if text != "/hello":
|
||||
await ctx.log("Message ignored: expected /hello")
|
||||
return
|
||||
if ctx.config.get("reply_enabled", True):
|
||||
await ctx.reply(ctx.config.get("greeting", "Welcome aboard!"))
|
||||
else:
|
||||
await ctx.log("Replies are disabled for this processor")
|
||||
```
|
||||
|
||||
`on_join` は参加したメンバーに挨拶し、`on_message` は `/hello` に返信します。「返信する」を無効にすると、ログだけを記録します。
|
||||
|
||||
`ctx.platform_event` は現在のイベント、`ctx.config` はこのプロセッサーの設定です。返信には `ctx.reply()`、ログの記録には `ctx.log()` を使います。関数の実行が終わると、イベントの処理も終了します。
|
||||
|
||||
## ストリーミング返信
|
||||
|
||||
`ctx.reply_stream()` で同じ返信を更新します。`update(text)` には、その時点の全文を渡します。
|
||||
|
||||
```python
|
||||
async with ctx.reply_stream() as reply:
|
||||
await reply.update("処理中…")
|
||||
await reply.update("完了しました。")
|
||||
```
|
||||
|
||||
`async with` ブロックが正常終了すると返信が完了します。ストリーミングが未対応・無効の場合やメッセージ以外のイベントでは、最後に全文を一度だけ送信します。例外時には、バッファ内の未完成テキストを送信しません。デバッグでは送信をシミュレーションします。
|
||||
|
||||
|
||||
## イベントの登録
|
||||
|
||||
`@self.handler(イベントクラス)` で処理関数を登録し、対応する識別子を `spec.events` に追加します。例えば `MemberJoinedEvent` は `group.member_joined` に対応します。
|
||||
|
||||
| イベント | マニフェストの識別子 | SDK のイベントクラス |
|
||||
| --- | --- | --- |
|
||||
| メッセージ受信 | `message.received` | `MessageReceivedEvent` |
|
||||
| メッセージ編集 | `message.edited` | `MessageEditedEvent` |
|
||||
| メッセージ削除 | `message.deleted` | `MessageDeletedEvent` |
|
||||
| メッセージへのリアクション | `message.reaction` | `MessageReactionEvent` |
|
||||
| フィードバック受信 | `feedback.received` | `FeedbackReceivedEvent` |
|
||||
| メンバー参加 | `group.member_joined` | `MemberJoinedEvent` |
|
||||
| メンバー退出 | `group.member_left` | `MemberLeftEvent` |
|
||||
| メンバーのミュート | `group.member_banned` | `MemberBannedEvent` |
|
||||
| グループ情報更新 | `group.info_updated` | `GroupInfoUpdatedEvent` |
|
||||
| 友達申請受信 | `friend.request_received` | `FriendRequestReceivedEvent` |
|
||||
| 友達追加 | `friend.added` | `FriendAddedEvent` |
|
||||
| 友達削除 | `friend.removed` | `FriendRemovedEvent` |
|
||||
| Bot のグループ招待 | `bot.invited_to_group` | `BotInvitedToGroupEvent` |
|
||||
| Bot のグループからの削除 | `bot.removed_from_group` | `BotRemovedFromGroupEvent` |
|
||||
| Bot のミュート | `bot.muted` | `BotMutedEvent` |
|
||||
| Bot のミュート解除 | `bot.unmuted` | `BotUnmutedEvent` |
|
||||
| プラットフォーム固有のイベント | `platform.specific` | `PlatformSpecificEvent` |
|
||||
|
||||
利用できるイベントとフィールドは、SDK の [events.py](https://github.com/langbot-app/langbot-plugin-sdk/blob/main/src/langbot_plugin/api/entities/builtin/platform/events.py)を参照してください。対応イベントはプラットフォームによって異なります。
|
||||
|
||||
## イベントプロセッサーのテスト
|
||||
|
||||
[プラグイン開発チュートリアル](/ja/plugin/dev/tutor)に従ってデバッグ接続を設定し、プラグインのディレクトリで `lbp run` を実行します。LangBot で次の操作を行ってください。
|
||||
|
||||
1. 「プラグインプロセッサー」を作成し、詳細画面で「新しいメンバーを歓迎」を選び、設定を保存します。
|
||||
2. 左側でメンバー参加を選択し、ニックネーム、メンバー ID、グループ ID を入力してテストを実行します。
|
||||
3. 返信とログを確認します。メッセージ受信を選んで `/hello` を入力すると、返信のテストもできます。
|
||||
|
||||
デバッグ中の返信は Mock で再現され、実際のメッセージは送信されません。コンポーネントの YAML を変更したら `lbp run` を再起動してください。
|
||||
|
||||
インストール後、ボット詳細の「プラグインプロセッサー」でこの設定を追加して保存します。この画面でコンポーネントを選択し、設定を入力して新規作成・紐付けすることもできます。宣言されたイベントは自動で配信され、イベントごとのルート設定は不要です。
|
||||
|
||||
プラグインの購読は Agent/パイプラインのルートと独立して実行されます。同じイベントを複数のプロセッサーが処理でき、一つが失敗しても他の実行を妨げません。同じ設定を使用するボットは設定内容と実行状態を共有します。個別に設定する場合は、新しい設定を作成してください。返信などの任意の動作はコンポーネントの設定として提供し、重複返信を避けてください。
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
1 つの Workspace でプラグインをインストールできるのは 1 回ですが、その installation は**複数のプロセッサ**(Agent / パイプライン)から再利用されます。Runner はプロセッサ間でデータが混ざらないよう自分で保証する必要があります:
|
||||
|
||||
- `ctx.state.*` はすでにプロセッサ単位で分離されています。スコープに `binding_identity`(Agent UUID 由来)が含まれるため、同じ installation の別プロセッサが互いを上書きしません。
|
||||
- プラットフォームのストレージキーは `(instance, Workspace, owner type, plugin, key)` で、**プロセッサの次元を含みません**。したがって自分で Host ストレージやキャッシュに書くキーには、プロセッサまたは会話の識別子も含めてください。
|
||||
- `RunnerContext`(`ctx`)は 1 回の実行でのみ有効です。インスタンスに保存して呼び出しをまたいで再利用しないでください。呼び出しをまたいで保持する会話状態は `ctx.state_get()` / `ctx.state_set()` を使います。
|
||||
- 複数のプロセッサは同じ Runner インスタンスを共有するため、現在の実行のデータをインスタンスフィールドに蓄積しないでください。長期常駐の外部セッションプロセスのように、プロセス内に呼び出しをまたぐ状態を持つ必要がある Runner は、共有実行を宣言せず専用のままにしてください。
|
||||
- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
- [`RunnerContext` API リファレンス](/ja/plugin/dev/apis/agent-run#runner)を確認します。
|
||||
- モデル、ツール、ナレッジベース、ストレージについては [LangBot API](/ja/plugin/dev/apis/common)を確認します。
|
||||
- メッセージ送信などについては[プラットフォーム API](/ja/plugin/dev/apis/platform)を確認します。
|
||||
- [RunnerDemo](https://github.com/langbot-app/langbot-plugin-demo/tree/main/Runner/RunnerDemo)で他の実装例を確認できます。
|
||||
- パイプラインの前処理やモデル呼び出し完了時などに機能を追加する場合は、[イベントリスナー](/ja/plugin/dev/components/event-listener)を使用してください。
|
||||
@@ -0,0 +1,188 @@
|
||||
---
|
||||
title: "コンポーネント: Tool"
|
||||
---
|
||||
|
||||
LangBotの組み込みLocal Agentは、実行中にツールを呼び出して外部世界と対話します。現在、プラグインとMCPの両方を通じてツールを追加することがサポートされています。
|
||||
|
||||
## Toolコンポーネントの追加
|
||||
|
||||
1つのプラグインには任意の数のツールを含めることができます。プラグインディレクトリでコマンド`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
|
||||
...
|
||||
```
|
||||
|
||||
## マニフェストファイル: Tool
|
||||
|
||||
```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/<tool_name>.py`)。`GetWeatherAlerts`クラスの`call`メソッドでこのツールの呼び出しと戻り値のロジックを実装する必要があります。
|
||||
|
||||
```python
|
||||
# Auto generated by LangBot Plugin SDK.
|
||||
# Please refer to https://langbot.app/docs/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
|
||||
from langbot_plugin.api.entities.builtin.provider import session as provider_session
|
||||
|
||||
|
||||
class GetWeatherAlerts(Tool):
|
||||
|
||||
async def call(self, params: dict[str, Any], session: provider_session.Session, query_id: int) -> str:
|
||||
"""Fill your tool code here"""
|
||||
return {}
|
||||
```
|
||||
|
||||
`call`メソッドでこのツールを実装します:
|
||||
|
||||
<Info>
|
||||
天気検索ツールの例は、MCPの[サーバー作成例](https://modelcontextprotocol.io/quickstart/server)から来ています。
|
||||
</Info>
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from langbot_plugin.api.definition.components.tool.tool import Tool
|
||||
from langbot_plugin.api.entities.builtin.provider import session as provider_session
|
||||
import httpx
|
||||
|
||||
# 定数
|
||||
NWS_API_BASE = "https://api.weather.gov"
|
||||
USER_AGENT = "weather-app/1.0"
|
||||
|
||||
|
||||
async def make_nws_request(url: str) -> dict[str, Any] | None:
|
||||
"""適切なエラー処理を使用してNWS APIにリクエストを行う。"""
|
||||
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:
|
||||
"""アラート機能を読みやすい文字列にフォーマットする。"""
|
||||
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], session: provider_session.Session, query_id: int) -> str:
|
||||
"""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="/images/zh/plugin/dev/components/tool_use_in_chat.png" />
|
||||
|
||||
## 1 つの installation、複数の用途
|
||||
|
||||
1 つの Tool は**複数のプロセッサと会話**から呼び出されます(1 つのイベントで複数のプラグインプロセッサが起動し得ます):
|
||||
|
||||
- 設定は呼び出しごとに読み取ってください(`self.get_plugin_config()` または `ctx.config`)。`initialize()` で読み取ってインスタンスにキャッシュしないでください。
|
||||
- 呼び出し引数、戻り値、会話データをインスタンスフィールドやモジュールレベルの変数に残さないでください。会話の識別子は現在の呼び出しの `session` / `query_id` から取得します。
|
||||
- 同時呼び出しは同じインスタンスを共有するため、インスタンスフィールドは書き込みが交錯します。呼び出しをまたいで保持するデータは Host ストレージに置き、対応するプロセッサまたは会話の識別子をキーに含めてください。
|
||||
- installation 単位でプロセス内キャッシュを保持する場合は、`on_installation_revoked(binding)` で解放してください。
|
||||
|
||||
完全な仕様は[認証プラグインと共有実行](/ja/plugin/certified-plugins)を参照してください。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
ツールの基本的な使い方を学びました。次は:
|
||||
|
||||
- Tool が受け取る [`session` と `query_id`](/ja/plugin/dev/apis/agent-run#tool)を確認します。
|
||||
- [LangBot API](/ja/plugin/dev/apis/common)を確認します。
|
||||
Reference in New Issue
Block a user