> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langbot.app/llms.txt
> Use this file to discover all available pages before exploring further.

# コンポーネント: パーサー

パーサー（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 theme={null}
➜  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 theme={null}
➜  MyParserPlugin > tree
...
├── components
│   ├── __init__.py
│   └── parser
│       ├── __init__.py
│       ├── pdf_parser.py
│       └── pdf_parser.yaml
...
```

## マニフェストファイル: パーサー

```yaml theme={null}
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 theme={null}
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 theme={null}
async def parse(self, context: ParseContext) -> ParseResult:
```

**ParseContext**には以下の情報が含まれます:

```python theme={null}
class ParseContext(pydantic.BaseModel):
    file_content: bytes          # 生のファイルバイト（LangBotがストレージから読み取り）
    mime_type: str               # 検出されたファイルのMIMEタイプ
    filename: str                # 元のファイル名
    metadata: dict[str, Any]     # FileObjectからの追加メタデータ
```

**ParseResult**は解析結果を返す必要があります:

```python theme={null}
class ParseResult(pydantic.BaseModel):
    text: str                            # 抽出された完全なプレーンテキスト
    sections: list[TextSection] = []     # 構造化セクション（オプション）
    metadata: dict[str, Any] = {}        # 解析メタデータ（例: page_count, language）
```

**TextSection**はドキュメントから抽出されたテキストのセクションを表します:

```python theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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
```

## パーサーのテスト

作成後、プラグインディレクトリでコマンド`lbp run`を実行してデバッグを開始します。その後、LangBotで:

1. 「ナレッジベース」ページに移動
2. ナレッジベースを選択してドキュメント管理に入る
3. ファイルのアップロード時、パーサーセレクターでプラグインが提供するパーサーを選択
4. アップロード後、ドキュメントが正しく取り込まれたことを確認
