契約書の差分比較をGPT-3を使って自動化する

![Table_with_progress___Mantine_UI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/e2f2572b-c7f9-22aa-eb88-5aa2b744eb90.png)

こんにちは！逆瀬川 ( https://twitter.com/gyakuse ) です！
今日は契約書の更新差分の比較をGPT-3とGoogle Document AIを使ってやってみたいと思います。

## 概要

異なるバージョンの契約書をPDF解析システムとGPT-3を使って自動的に比較し、リスクなどの検討も自動で出力する仕組みを作る。

## Colab

https://colab.research.google.com/drive/1jgLcnkYaFsMgjM_8kyRMD5-TQXmNTdnF?usp=sharing

### 使い方

- Document AIの準備 (作成方法など詳しくは後述)
    - Google CloudのDocument AIでプロセッサを作成する
    - プロセッサ一覧から作成したプロセッサを選び、予測エンドポイントをコピーする
        - https://console.cloud.google.com/ai/document-ai/processors
    - json形式のAPIキーファイルをダウンロードしておく
- OpenAI APIキーの準備
- すべてのセルを実行
- 比較したいドキュメントを貼り付ける
- 注意
    - 業務で使う場合、OpenAI等のオプトアウトを事前に適用する必要があります
        - [参考](https://qiita.com/sakasegawa/items/3855472a8566ea302a99#%E3%83%97%E3%83%AD%E3%83%B3%E3%83%97%E3%83%88%E3%81%A8%E3%81%97%E3%81%A6%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%82%92%E5%AE%89%E5%85%A8%E3%81%AB%E6%B8%A1%E3%81%99%E6%96%B9%E6%B3%95%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6-2%E6%9C%8816%E6%97%A5%E8%BF%BD%E5%8A%A0)

## 差分比較が自動でできると何が嬉しいのか

契約書では細かい文言の修正等があり、ときには数十箇所におよぶ。重要箇所の見逃しのリスクもあるため、まとめて見ておきたい

## やりたいこと

- 2つの契約書における差分を明文化する
- 修正された部分がどのようなリスクを生むか検討する

## どのように契約書の差分を取るか検討する

契約書は通常pdfで渡されます。文字埋め込みがあることがほとんどであるため、それを前提として良いでしょう。むずかしそうなのが、同一セクションをどうやって判別するかというところです。手法としては以下が考えられます。

- レイアウト検知系の処理をかけて、かつパラグラフを表す文字列を突合させる
- pdfからテキストを抽出し、テキストからパラグラフ単位に分け、パラグラフ同士の類似性から同一セクションを判定する

今回は簡単のため、前者を利用します。

## レイアウト検知ライブラリを選ぶ

### LayoutParser

以前論文のレイアウト検知に使ったLayoutParserを使ってみます。
事前学習モデルとしては Publaynet の `mask_rcnn_X_101_32x8d_FPN_3x` を使いました。

![契約書の差分検討withgpt3_ipynb_-_Colaboratory.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/4fbec882-c964-6764-ee27-01bcfe116e06.png)

やはり、科学論文向けの model であるため、検出精度は低めです。

### Azure Form Recognizer

[以前の記事](https://qiita.com/sakasegawa/items/78c66fc83eda03b784da)で使ったAzureのForm Recognizerを使ってみると、文字列はすべて取得されますが、レイアウト検知としては粒度が細かすぎる状態となってしまいました。

![Form_Recognizer_Studio_-_Microsoft_Azure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/d8cb7d4f-3178-519e-a256-7dca59ba9d6e.png)

### Google Document AI

一方、Google の Document AIを用いると期待通りの結果となりました。

![Document_OCR_分析_–_Document_AI_–_My_First_Project_–_Google_Cloud_コンソール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/78c8739c-b125-92be-faf9-ce49ee9f9a2f.png)

今回は差分比較について行いたいので、レイアウト検出はこちらに任せます。

## Google Document AI

https://console.cloud.google.com/ai/document-ai?hl=ja

上記ページから Document AI のプロセッサを作成しておきます。
基本的な使い方はLayerXさんのブログ記事や公式のドキュメントを参考にすると良いです。

https://tech.layerx.co.jp/entry/2022/12/18/080000

https://cloud.google.com/document-ai/docs/send-request

## 実装

### 契約書を作る

契約書がなければ話が始まらないのですが、実験で使いやすい雛形がないためGPT-3に生成してもらいます。

![ソフトウェア開発契約.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/e3c7488b-502c-96c5-c041-fc47ad9c1b3e.png)

また、今回は差分の自動検討のため、これに修正を加えたものを用意する。
主な修正点は以下。

- 第1条の業務委託内容に `継続的な保守` を追加
- 第3条の `支払い条件：業務完了後、30日以内に支払う。` という条件を削除
- 第6条（秘密保持）をすべて削除
- 第8条の免責事項の `以下のような事由によって損害が生じた場合でも` という表現を `以下のような事由によって損害が生じた場合に限り` に変更

作成した契約書と修正契約書を以下に掲載します。

https://drive.google.com/drive/folders/1gHsQNLUokVbkuPRYpRQfZTRgm1ZUC9Zb

### Document AIでOCRしてパラグラフを分解する

処理等はドキュメントにある通り実装します(詳しい実装を見たい場合はColab参照のこと)。
エンドポイントパーサだけ工夫しました。

```py
from urllib.parse import urlparse
import re

def parse_endpoint(endpoint_url):
    result = urlparse(endpoint_url)
    path = result.path
    parts = re.split(r"/", path)

    project_id = parts[3]
    location = parts[5]
    processor_id = parts[7].split(":")[0]
    return location, project_id, processor_id
```

### パラグラフに分ける

これもドキュメント通りではありますが、それぞれのparagraphをテキストにします。
layout_to_textの実装は[こちらのドキュメント](https://cloud.google.com/document-ai/docs/handle-response)のままです。

```py
a_paragraph_texts = [layout_to_text(paragraph.layout, a_document.text) for paragraph in a_document.pages[0].paragraphs]
b_paragraph_texts = [layout_to_text(paragraph.layout, b_document.text) for paragraph in b_document.pages[0].paragraphs]
```

### パラグラフを突合させる

上記までで、修正前・修正後それぞれのpdfの各パラグラフの文章が取れているので、
編集距離(特にレーベンシュタイン距離)を使って、対応する条項をペアにし、対応しない条項をそれぞれdelete_paragraph、add_paragraphに入れます。

```py
import Levenshtein

pair_paragraphs = []
delete_paragraphs = []
add_paragraphs = []

for a_paragraph in a_paragraph_texts:
    is_match = False
    for index, b_paragraph in enumerate(b_paragraph_texts):
        lev_ratio = Levenshtein.ratio(a_paragraph, b_paragraph)
        # 0.75未満のものは新規追加項とみなす
        if lev_ratio > 0.75:
            pair_paragraphs.append([a_paragraph, b_paragraph])
            b_paragraph_texts.pop(index)
            is_match = True
    if not is_match:
        delete_paragraphs.append(a_paragraph)
add_paragraphs = b_paragraph_texts
```

### GPT-3を使って対応する条項の変更ポイントを見る

以下のような簡単なプロンプトを用意しました。

```py
def prompt_template_change_risk(before, after):
    return f"""以下は契約書における変更内容である

## 変更前

{before}

## 変更後

{after}

## タスク

Q: 変更された内容とこの変更による委託先側のリスクについて簡潔に答えよ
A:"""

def prompt_template_append_risk(text):
    return f"""以下は契約書における追加内容である

## 追加内容

{text}

## タスク

Q: 追加された内容による委託先側のリスクについて簡潔に答えよ
A:"""

def prompt_template_delete_risk(text):
    return f"""以下は契約書における削除内容である

## 削除内容

{text}

## タスク

Q: この内容が削除されたことによる委託先側のリスクについて簡潔に答えよ
A:"""
```

## やっていないこと

- 複数ページ対応などはしていません

## 後記

- DocumentAIおよびGPT-3を使うと簡単に契約書差分比較ができることがわかってよかった
    - いっぽうで、契約書の解釈として怪しい部分もある
- プロンプトエンジニアリングがんばるともっといい感じにできそう
    - リスクをパーセンテージで出すとかはそこまでたいへんでもない
    - 以下のようなUIだとうれしみが発生しそう
        - ![Table_with_progress___Mantine_UI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/265933f5-74bc-0b7a-3c9e-a373ededb8ed.png)
- 上記のような法務AI系だと[GVA assist](https://ai-con-pro.com/)さんや[LegalForce](https://legalforce-cloud.com/)さんがあるみたいです。あまり知らない領域だったのですが、メチャクチャ便利になっててビビりました