Integration & Customization

連携・カスタマイズガイド

プロンプトのカスタマイズで独自 JSON を取り出す方法と、文書種別ごとのカスタムハンドラをプログラムする方法を解説します。

文書タイプ別 AI プロンプトのカスタマイズ

各 DocumentClass に設定するプロンプトを工夫することで、AI の出力 JSON に固有のフィールドを追加できます。 抽出したい項目と形式を自然言語で指示するだけで、構造化データとして取り出せます。

1プロンプト全体の構造を理解する

AI へ送られるプロンプトは、システム共通部分各 DocumentClass ごとの追記部分が結合されたものです。 管理画面で DocumentClass に設定するテキストは、後者の「追記部分」にあたります。

 AI へ送られるプロンプトの全体構造(概念図)
# システム共通プロンプト(変更不可)
PDFまたは画像ファイルの内容を読み取り、文書の種類を判定してください。
JSONのみを返答してください。...

fax_properties: { ... FAXヘッダー情報 }
content_properties: { ... 本文の共通項目 }
typed_properties: { ... ★ 各クラスの指示で追加するフィールド }

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=ypl  ← DocumentClass の区切り文字

### Invoice(管理画面で設定した DocumentClassID)
DocumentClassID  Invoice

↑ここから下が管理画面の「Prompt」フィールドに書いたテキスト

請求書と判定するための条件と、抽出すべき項目…

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=ypl

### OrderForm(次の DocumentClass)
DocumentClassID  OrderForm
ℹ️

管理画面の「Prompt」欄に書いたテキストは、そのままの形で AI へ渡されます。Markdown 形式で箇条書きや見出しを使うと AI が読みやすくなります。

2基本の出力 JSON スキーマ

すべての分類結果に共通して出力されるフィールドです。DocumentClass のプロンプトで指示した内容は typed_properties に格納されます。

 AI 出力 JSON の共通スキーマ
{
  "documentClassId": "Invoice",         // 判定された DocumentClassID(null = 不明)
  "confidence":      0.95,             // 分類確信度(0.0 〜 1.0)
  "fax_properties": {               // FAX ヘッダー/フッターから読み取った伝送情報
    "senderName":            "...",
    "senderFaxNumber":        "...",
    "recipientName":          "...",
    "recipientFaxNumber":      "...",
    "transmissionTimestamp":   "2026-02-18T08:18:00",
    "totalPages":             3,
    "jobId":                  "..."
  },
  "content_properties": {           // 本文から読み取った共通情報
    "title":                 "請求書",
    "senderName":            "XX 商事株式会社",
    "senderFaxNumber":        "03-1234-5678",
    "senderPhoneNumber":      "03-1234-5679",
    "recipientName":          "担当者名",
    "recipientFaxNumber":      "...",
    "recipientPhoneNumber":    "...",
    "timestamp":              "2026-02-10T00:00:00"
  },
  "typed_properties": {             // ★ DocumentClass のプロンプトで追加した固有フィールド
    // (後述のカスタマイズ例を参照)
  },
  "notes": ""                        // 補足コメント(AI が任意で記入)
}

3typed_properties に独自フィールドを追加する

DocumentClass の「Prompt」フィールドに、typed_properties へ格納してほしい項目をJSON形式のサンプルとともに指示します。 AI はその指示に従って、文書画像から該当する値を読み取り出力します。

 管理画面 → DocumentClass「Invoice」の Prompt 記述例
## 分類条件

次のいずれかに当てはまる場合、この文書を Invoice(請求書)と判定してください。

- 「請求書」「御請求書」「Invoice」などの見出しがある
- 請求金額・振込先口座・請求日が記載されている
- 発行者の印鑑または社名スタンプがある

## 抽出フィールド

以下のフィールドを typed_properties に格納してください。
読み取れない場合は null としてください。

```json
{
  "typed_properties": {
    "invoiceNumber":    "請求書番号(文字列)",
    "invoiceDate":      "請求日(ISO 日付文字列 YYYY-MM-DD)",
    "dueDate":          "支払期限(ISO 日付文字列 YYYY-MM-DD)",
    "totalAmount":      "請求合計金額(税込、数値)",
    "taxAmount":        "消費税額(数値)",
    "currency":         "通貨コード(例: JPY)",
    "bankName":         "振込先金融機関名",
    "bankBranch":       "振込先支店名",
    "accountType":      "口座種別(普通/当座)",
    "accountNumber":    "口座番号",
    "accountHolder":    "口座名義",
    "lineItems": [
      {
        "description": "品目・摘要",
        "quantity":    "数量(数値)",
        "unitPrice":   "単価(数値)",
        "amount":      "金額(数値)"
      }
    ]
  }
}
```

ポイント:JSON サンプルを使って指示する
抽出してほしい構造を JSON サンプルとしてプロンプト内に記述すると、AI が出力する JSON のキー名・型・構造を確実に制御できます。値部分を日本語の説明にしておくと、何を読み取ればよいかが AI に伝わりやすくなります。

4カスタマイズ後の出力 JSON 例

上記のプロンプトを設定した場合、AI は次のような JSON を出力します。この内容がそのまま DB の DocumentData 列に格納されます。

 AI 出力例(Invoice クラスにカスタムフィールドを追加した場合)
{
  "documentClassId": "Invoice",
  "confidence": 0.97,
  "fax_properties": { ... },
  "content_properties": {
    "title": "御請求書",
    "senderName": "XX 商事株式会社",
    "timestamp": "2026-02-10T00:00:00"
    ...
  },
  "typed_properties": {
    "invoiceNumber":  "INV-2026-0042",
    "invoiceDate":    "2026-02-10",
    "dueDate":        "2026-02-28",
    "totalAmount":    110000,
    "taxAmount":      10000,
    "currency":       "JPY",
    "bankName":       "〇〇銀行",
    "bankBranch":     "渋谷支店",
    "accountType":    "普通",
    "accountNumber":  "1234567",
    "accountHolder":  "ヨキンソフト",
    "lineItems": [
      { "description": "システム保守費", "quantity": 1, "unitPrice": 100000, "amount": 100000 }
    ]
  },
  "notes": ""
}

plugins フォルダへのカスタムハンドラの実装

plugins/ フォルダに特定の命名規則でPythonファイルを置くと、該当する文書種別のファイルが正常に処理された直後に自動で呼び出されます。 外部システムへの転送・メール通知・連携 API 呼び出しなど、業務固有の後処理を自由にプログラムできます。

1ファイルの命名規則と配置場所

 プロジェクト構成(plugins フォルダ)
YokinsPaperless/
 ├── monitor/
 │    └── monitor.py
 └── plugins/                           ← ここにハンドラを配置
      ├── docClassHandler_Invoice.py    ← DocumentClassID が "Invoice" の場合に呼び出される
      ├── docClassHandler_OrderForm.py  ← DocumentClassID が "OrderForm" の場合に呼び出される
      └── docClassHandler_Notice.py     ← DocumentClassID が "Notice" の場合に呼び出される

2handle_document(document) 関数を実装する

ハンドラファイルには handle_document(document) という関数を定義します。 引数 documentDocuments テーブルの1行を dict として渡したものです。

 plugins/docClassHandler_Invoice.py — 最小構成
def handle_document(document):
    """
    document: Documents テーブルの1行を表す dict
    正常処理が完了した直後に呼び出される。
    戻り値は無視される。例外をスローしてもシステムに影響しない。
    """
    pass

3引数 document の内容

document は以下のキーを持つ辞書です。DocumentData の値は JSON 文字列なので、json.loads() でパースして使います。

キー内容
IDstr文書 UUID
Activeint有効フラグ(1 = 有効)
SourcePathstr元の FAX ファイルの絶対パス
DateCreatedstrDB 登録日時(ISO)
DateReceivedstrFAX 受信日時(ISO)
TitlestrAIが抽出したタイトル
Senderstr送信者名
SenderOrganizationstr送信者 FAX 番号
Recipientstr受信者名
RecipientOrganizationstr受信者 FAX 番号
DocumentClassIDstr文書クラス ID(このハンドラを呼んだクラス)
DocumentDatastrAI 出力 JSON 全体の文字列(json.loads でパース)

4実装例

例 1:ログファイルに書き出す

 docClassHandler_Invoice.py
import json, pathlib, datetime

def handle_document(document):
    data = json.loads(document["DocumentData"] or "{}")
    tp   = data.get("typed_properties", {})

    log_path = pathlib.Path("/var/log/paperless/invoice.log")
    log_path.parent.mkdir(parents=True, exist_ok=True)

    line = f'[{datetime.datetime.now().isoformat()}] ' \
           f'id={document["ID"]} ' \
           f'no={tp.get("invoiceNumber")} ' \
           f'amount={tp.get("totalAmount")}\n'

    with log_path.open("a", encoding="utf-8") as f:
        f.write(line)

例 2:メール通知を送る

 docClassHandler_Invoice.py
import json, smtplib
from email.mime.text import MIMEText

SMTP_HOST = "localhost"
NOTIFY_TO = "[email protected]"

def handle_document(document):
    data = json.loads(document["DocumentData"] or "{}")
    tp   = data.get("typed_properties", {})
    amt  = tp.get("totalAmount", "不明")
    subj = f'[請求書受信] {document["Sender"]} / {amt}円'
    body = (
        f'文書ID : {document["ID"]}\n'
        f'受信日時: {document["DateReceived"]}\n'
        f'送信者 : {document["Sender"]}\n'
        f'金額   : {amt}\n'
        f'ファイル: {document["SourcePath"]}\n'
    )
    msg = MIMEText(body, "plain", "utf-8")
    msg["Subject"] = subj
    msg["From"]    = "[email protected]"
    msg["To"]      = NOTIFY_TO
    with smtplib.SMTP(SMTP_HOST) as s:
        s.send_message(msg)

例 3:外部 Web API へ POST する

 docClassHandler_Invoice.py
import json, urllib.request

WEBHOOK_URL = "https://erp.example.com/api/invoices"
API_TOKEN   = "Bearer my-secret-token"

def handle_document(document):
    data = json.loads(document["DocumentData"] or "{}")
    tp   = data.get("typed_properties", {})

    payload = {
        "source":        "paperless",
        "documentId":    document["ID"],
        "receivedAt":    document["DateReceived"],
        "sender":        document["Sender"],
        "invoiceNumber": tp.get("invoiceNumber"),
        "totalAmount":   tp.get("totalAmount"),
        "lineItems":     tp.get("lineItems", []),
    }
    body = json.dumps(payload, ensure_ascii=False).encode()
    req  = urllib.request.Request(
        WEBHOOK_URL, data=body,
        headers={
            "Content-Type":  "application/json",
            "Authorization": API_TOKEN,
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=10) as r:
        print(f"[plugin] ERP response: {r.status}")

例 4:元ファイルを別フォルダにコピーする

 docClassHandler_Invoice.py
import json, shutil, pathlib, datetime

ARCHIVE_DIR = pathlib.Path("/mnt/nas/invoice-archive")

def handle_document(document):
    data = json.loads(document["DocumentData"] or "{}")
    tp   = data.get("typed_properties", {})

    yyyymm = datetime.datetime.now().strftime("%Y%m")
    dst_dir = ARCHIVE_DIR / yyyymm
    dst_dir.mkdir(parents=True, exist_ok=True)

    src = pathlib.Path(document["SourcePath"])
    inv_no = tp.get("invoiceNumber") or document["ID"][:8]
    dst = dst_dir / f"INV_{inv_no}_{src.name}"

    shutil.copy2(src, dst)
    print(f"[plugin] copied to {dst}")

5動作の仕様と注意事項

⚠️

長時間かかる処理はスレッドに逃がす: ハンドラは同期的に呼ばれるため、外部 API やメール送信など時間がかかる処理はハンドラ内で threading.Thread を使って非同期実行するか、タイムアウトを短めに設定してください。

ℹ️

環境変数の利用: SMTP ホスト名・API エンドポイント・認証トークンなどは、プロジェクトルートの .env ファイルに書いておくと、ハンドラ内から os.getenv() で取得できます。ソースコードへの直書きは避けてください。