こんにちは、MNTSQでエンジニアをやっている平田です。 MNTSQでは自然言語処理を使って契約書を解析したり検索したりする機能を開発しています。
契約書解析には、次のようなタスクがあります。
- 秘密保持契約等の契約類型に分類
- 契約締結日や契約当事者等の基本情報を抽出
- 条項(第1条, 第2条, ...)単位で分解
本稿では、これらの契約書解析タスクをGPT-4oに解かせてどんな結果になるか見てみます。
ざっくりやり方
GPT-4oのAPIを呼び出すところ
ここではAzure OpenAIのGPT-4oを使います。Microsoftのサンプルコードほぼそのままですが、一応貼り付けておきます。
from openai import AzureOpenAI client = AzureOpenAI( api_version="2023-05-15", azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), api_key=os.getenv("AZURE_OPENAI_API_KEY"), ) response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "ここにプロンプトを入れる"}], temperature=0, ) completion = response.choices[0].message.content
解析結果をJSON形式で出力させるプロンプトテンプレート
解析結果をソフトウェアで扱いやすくするために、JSON形式で出力させます。 JSON形式で出力させるテクニックはいくつかありますが、ここではプロンプトでJSONスキーマを渡します。 プロンプトのテンプレートは次のようなイメージです。
prompt_template = """\ {content} Transform the above text into a JSON according to the following JSON schema. Output in a code block, and do not output anything else. ```json {json_schema} ``` """
PydanticでJSONスキーマを生成するところ
PydanticはJSONスキーマの生成とバリデーションができるので、今回やりたいことにぴったりです。
Pydanticを使わずにJSONスキーマを直接書いてもよいのですが、JSONスキーマは複雑すぎて少なくとも私は読みたくないですし、JSONスキーマ自体のバリデーションが必要になるのでPythonで書いてmypyに静的解析させるほうが楽だと思います。
Pydanticで次のようなデータモデルを定義します。
from pydantic import BaseModel, Field class ClauseResponse(BaseModel): number: str | None = Field(default=None, description="条番号") heading: str | None = Field(default=None, description="条見出し") class ContractResponse(BaseModel): document_name: str | None = Field(default=None, description="ドキュメントのタイトル") is_contract: bool = Field(description="契約書のテキストかどうか") execution_date: str | None = Field(default=None, description="契約を締結した日") effective_date: str | None = Field(default=None, description="契約の効力が発生した日") renewal_term: str | None = Field(default=None, description="契約の自動更新期間") notice_to_terminate_renewal: str | None = Field(default=None, description="契約の自動更新を終了するための条件") governing_law: str | None = Field(default=None, description="契約の準拠法") parties: list[str] = Field(default_factory=list, description="契約の当事者名リスト") clauses: list[ClauseResponse] = Field(default_factory=list, description="契約の条項リスト")
JSONスキーマは ContractResponse.model_json_schema()
で生成できます。
{ "$defs": { "ClauseResponse": { "properties": { "number": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "条番号", "title": "Number" }, "heading": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "条見出し", "title": "Heading" } }, "title": "ClauseResponse", "type": "object" } }, "properties": { "document_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "ドキュメントのタイトル", "title": "Document Name" }, "is_contract": { "description": "契約書のテキストかどうか", "title": "Is Contract", "type": "boolean" }, "execution_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約を締結した日", "title": "Execution Date" }, "effective_date": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の効力が発生した日", "title": "Effective Date" }, "renewal_term": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の自動更新期間", "title": "Renewal Term" }, "notice_to_terminate_renewal": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の自動更新を終了するための条件", "title": "Notice To Terminate Renewal" }, "governing_law": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "default": null, "description": "契約の準拠法", "title": "Governing Law" }, "parties": { "description": "契約の当事者名リスト", "items": { "type": "string" }, "title": "Parties", "type": "array" }, "clauses": { "description": "契約の条項リスト", "items": { "$ref": "#/$defs/ClauseResponse" }, "title": "Clauses", "type": "array" } }, "required": [ "is_contract" ], "title": "ContractResponse", "type": "object" }
GPT-4oが出力したJSON文字列を抽出する
プロンプトで Output in a code block
と指示しているので、GPT-4oはコードブロックで囲われたJSONを出力します。
ここでは正規表現を使ってコードブロックからJSON文字列を抽出します。
import re json_pattern = re.compile(r"```json\n(.+?)\n```", re.DOTALL) if m := json_pattern.search(completion): json_text = m.group(1)
PydanticでJSON文字列をバリデーションするところ
Pydanticを使うとJSON文字列がJSONスキーマに従っているかバリデーションできます。
json_object = ContractResponse.model_validate_json(json_text)
もしバリデーションでエラーになった場合は、次のようなテンプレートでエラーメッセージをプロンプトに追加するとうまく修正してくれることがあります。(まあGPT-4oだとほぼ完璧なJSONを返してくれるので使う機会のないテクニックですが...)
"""\ The following errors occurred, fix it. ``` {errors} ``` """
実際にやってみた
MNTSQにたくさんある契約書サンプルの1つを使って実験してみます。
上記契約書からOCRで抽出したテキストを入力すると、GPT-4oから次のような出力が返ってきます。
```json { "document_name": "金銭消費貸借契約書", "is_contract": true, "execution_date": "2021-04-20", "effective_date": "2021-04-30", "renewal_term": null, "notice_to_terminate_renewal": null, "governing_law": null, "parties": [ "株式会社フォイエルバッハ商事", "MNTSQ株式会社", "板谷隆平" ], "clauses": [ { "number": "1", "heading": "消費貸借" }, { "number": "2", "heading": "借入条件" }, { "number": "3", "heading": "連帯保証" }, { "number": "4", "heading": "期限の利益の喪失" }, { "number": "5", "heading": "届出義務" }, { "number": "6", "heading": "反社会的勢力の排除" }, { "number": "7", "heading": "公正証書の作成" }, { "number": "8", "heading": "費用負担" }, { "number": "9", "heading": "管轄" } ] } ```
GPT-4oの出力からJSON文字列を抽出し、Pydanticのデータモデルでバリデーションしたところ、エラーなく読み込めました。 解析結果の値も完璧です 🚀
実は execution_date
(締結日)と effective_date
(発効日)って別物なのですが、これらをちゃんと区別できていますね。すごい!
感想
ちょっと前まで、契約書解析は次のような工程で開発していて、1機能リリースするのに数ヶ月かかることが通常でした。
ChatGPTを使った開発では、アノテーションとモデリングの代わりにプロンプトエンジニアリングを行います。 これにより次のような嬉しさ(開発のスケールアウト)があります。
GPT-4でも同様の開発はできるのですが、APIの料金が高く選択肢に入りにくい状況でした。 一方、GPT-4oはGPT-4に比べて破壊的に安く、選択肢に入るビジネスモデルが大幅に増えたのではないかと思います。
こうやってできることが増えていくとワクワクしますね 🙌
残る課題
これでAIが仕事を奪ってくれて明日から遊べるぞ!!!!とはならないのが残念なのですが、実際に契約書解析のプロダクトでGPT-4oを使う上でどんな課題があるか、いくつか挙げてみます。主に契約書というデータが長過ぎることに起因するものです。
- GPT-4oのトークン制限数
- Lost in the Middle
- こちらの論文にもあるように、プロンプトの中間にある情報は忘れられがちです。契約書は長いのでこの問題が結構クリティカルです。
- GPT-4oの料金
- GPT-4oのターンアラウンドタイム
- 爆速!と言われたGPT-4oですが、60ページくらいの契約書データを使ってプロンプト入力からJSON出力の時間を測ると1分くらいでした。サービスで提供したい体験によっては許容できない可能性があります。
他にもありますが、MNTSQではこのような課題と向き合いながらLLMを活用したプロダクト開発を行っています。
もしこの記事に興味をお持ちいただいて、「他のデータだとどうなの?」とか「課題は解決したの?」とか聞いてみたい方がいらっしゃいましたらお気軽にDM*1等でお問い合わせください。