1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VBAと生成AIの連携上級編ーVBA×生成AIで作る契約書自動レビューシステム【リスク検出・修正案生成】ー

1
Last updated at Posted at 2026-02-02

VBA×生成AIで作る契約書自動レビューシステム【リスク検出・修正案生成】

はじめに

初級編中級編と続いてきた本シリーズの完結編です。

初級編ではExcel単一セルの校正、中級編では50個のWord文書の一括レビューを実現しました。

今回の上級編では、ビジネス文書の中でも特に重要な「契約書」を専門的にレビューするシステムを構築します。

中級編の最後に、予告として

次回「上級編」では以下の機能を追加します:
• Power Automate連携 - SharePoint監視→自動レビュー
• 承認ワークフロー - レビュー結果に応じた自動承認
• 監査ログ・統計 - 処理履歴の完全記録
• Teams通知 - 処理完了を自動通知
• Power BIダッシュボード - 文書品質の可視化
「VBA×Power Automate×生成AIで作る企業向け文書管理システム」

と書きましたが、結果的にすべてPowerAutomateでできると分かったため、VBAと生成AIの連携記事にはならないと思い、内容を変更しました。
待ってくださった方は申し訳ございません。

上級編で実現すること

中級編との違いを表で示します。

項目 中級編 上級編(契約書レビュー)
対象文書 一般的な報告書 契約書(法的文書)
レビュー観点 文体・表現 法的リスク・条項の妥当性
処理段階 1回のレビュー 4段階レビュー
出力形式 Excelレポート Word文書にコメント挿入
リスク評価 なし 高・中・低の3段階
修正案 簡単な指摘のみ 具体的な修正案を生成
類似条項検索 なし 過去契約書から検索(RAG)
可視化 テキスト ダッシュボード(Excel)

追加される7つの主要機能は以下の通りです。

  1. 条項自動識別 - 秘密保持、支払条件、解約条件等を自動分類
  2. リスク評価 - 各条項のリスクレベルを3段階で評価
  3. 法的問題点検出 - 一方的に不利な条項を自動検出
  4. 修正案生成 - 具体的な修正文を提案
  5. Word文書コメント挿入 - リスクレベルに応じて色分けコメント
  6. 過去契約書検索 - 類似条項から学習(RAG)
  7. リスクダッシュボード - 契約書全体のリスクを可視化

想定される実務シーン

シーン1:取引先から送られてきた契約書のレビュー

従来の手動プロセス

【月曜日 10:00】
営業部が取引先から契約書(Word)を受領
  ↓
【10:30】
法務部に確認依頼メール
  ↓
【翌日 15:00】
法務担当者が契約書を精読(30分)
問題点をメールで回答
  ↓
【翌々日 10:00】
営業部が修正版を作成
再度法務部に確認依頼
  ↓
【翌々日 15:00】
法務担当者が再レビュー(20分)
  ↓
【翌々日 17:00】
最終承認

所要時間:3日間
法務担当者の工数:50分

上級編システム導入後

【月曜日 10:00】
営業部が取引先から契約書を受領
  ↓
【10:01】
Excelマクロを実行(Alt+F8)
  ↓
【10:02】
契約書レビュー完了(1分)
- リスク高:3箇所(赤コメント)
- リスク中:5箇所(黄コメント)
- リスク低:2箇所(緑コメント)
- 修正案:すべて自動生成済み
  ↓
【10:10】
営業部が修正案を確認、採用
  ↓
【10:20】
法務部に最終確認依頼(重要箇所のみ)
  ↓
【10:30】
法務担当者が赤コメント箇所のみ確認(5分)
  ↓
【10:35】
承認

所要時間:35分
法務担当者の工数:5分

効果(想定)

  • 処理時間:3日間 → 35分(99.2%削減)
  • 法務工数:50分 → 5分(90%削減)
  • 見落としリスク:大幅削減

このように、手動で契約書確認をすると見落としなどのリスクもあったのですが、
それを防ぐことができるようになるというのが大きなメリットです。


シーン2:自社で作成した契約書の事前チェック

従来

営業部が契約書ドラフト作成
  ↓
法務部に確認依頼
  ↓
法務部が精読(1時間)
  ↓
修正指示
  ↓
営業部が修正
  ↓
再確認(30分)
  ↓
承認

所要時間:2日間
法務工数:1.5時間

システム導入後

営業部が契約書ドラフト作成
  ↓
自分でマクロ実行(1分)
  ↓
リスク箇所を確認、即座に修正
  ↓
法務部に確認依頼(問題なしの見込み)
  ↓
法務部が最終確認(10分)
  ↓
承認

所要時間:30分
法務工数:10分

ここで重要なのは、法務部と契約書に関するルールなどの意識合わせをきちんとしておく必要があるということです。逆に言えば、そこをクリアしていれば自動化ツールにチェック項目を組み込める=ヒューマンエラーが削減されるということですね。


システム全体像

処理フロー

┌──────────────────────┐
│  契約書(Word/PDF)   │
└──────┬───────────────┘
       │
       ▼
┌──────────────────────┐
│  VBA:文書読み込み    │
│  - 条項ごとに分割     │
│  - 見出し抽出         │
└──────┬───────────────┘
       │
       ▼
┌──────────────────────┐
│  第1段階:条項識別    │
│  OpenAI API          │
│  - 秘密保持条項       │
│  - 支払条件           │
│  - 解約条件 等        │
└──────┬───────────────┘
       │
       ▼
┌──────────────────────┐
│  第2段階:リスク評価  │
│  OpenAI API          │
│  - 高リスク(赤)     │
│  - 中リスク(黄)     │
│  - 低リスク(緑)     │
└──────┬───────────────┘
       │
       ▼
┌──────────────────────┐
│  第3段階:問題点検出  │
│  OpenAI API          │
│  - 一方的に不利な条項 │
│  - 曖昧な表現         │
│  - 欠落している条項   │
└──────┬───────────────┘
       │
       ▼
┌──────────────────────┐
│  第4段階:修正案生成  │
│  OpenAI API          │
│  - 具体的な修正文     │
│  - 根拠説明           │
└──────┬───────────────┘
       │
       ▼
┌──────────────────────┐
│  VBA:結果を統合      │
│  - Wordコメント挿入   │
│  - Excelダッシュボード│
└──────┬───────────────┘
       │
       ▼
┌──────────────────────┐
│  レビュー済み契約書   │
│  + ダッシュボード     │
└───────────────────────┘

必要なもの

1. Excel VBA

役割

  • 契約書の読み込み
  • AIへのリクエスト送信
  • 結果の統合と出力

必要なバージョン

  • Excel 2016以降(VBA対応)

2. Microsoft Word

役割

  • 契約書の読み込み
  • コメント挿入

必要なバージョン

  • Word 2016以降

3. OpenAI API

役割

  • 条項識別
  • リスク評価
  • 問題点検出
  • 修正案生成

必要なもの

料金目安

  • gpt-4o:入力$2.50/1M tokens、出力$10.00/1M tokens
  • 10ページの契約書1件あたり約10~30円
  • 月20件レビューしても約200~600円

推奨モデル

  • gpt-4o(精度重視)
  • gpt-4o-mini(コスト重視)

4. 契約書サンプル

準備するもの

  • レビュー対象の契約書(Word形式推奨)
  • (オプション)過去の契約書データベース(RAG用)

手順(ステップ1~ステップ5)

それでは、実際にシステムを構築していきます。

全体の所要時間:約30分


ステップ1:前提条件の確認(5分)

●チェックリスト

以下を確認してください

  • Excel 2016以降がインストールされている
  • Word 2016以降がインストールされている
  • OpenAI APIキーを持っている
  • レビュー対象の契約書(Word形式)を準備している

すべてチェックできましたか

→ はい:次のステップへ進んでください


ステップ2:VBAマクロの配置(10分)

2-1. Excelファイルを作成

  1. Excelを起動

  2. 新規ブックを開く

  3. 「名前を付けて保存」

ファイル名:契約書レビューシステム.xlsm
ファイルの種類:Excelマクロ有効ブック (*.xlsm)
保存場所:デスクトップ

2-2. VBAエディタを開く

  1. Alt + F11 を押す

  2. VBAエディタが開きます

2-3. 標準モジュールを追加

  1. メニューから「挿入」→「標準モジュール」

  2. 左側のプロジェクトエクスプローラーに「Module1」が追加されます

2-4. VBAコードを貼り付け

  1. 以下「VBAコード全文」セクションから全コードをコピー
  2. VBAエディタのModule1に貼り付け(Ctrl + V

以下のコードをExcelのVBAエディタに貼り付けてください。
※変数は分かりやすくするためにあえて日本語で書いています

Option Explicit

' ===================================
' 契約書自動レビューシステム(上級編)
' VBA×生成AI
' ===================================

' API設定
Private Const API_KEY As String = "YOUR_OPENAI_API_KEY"
Private Const API_ENDPOINT As String = "https://api.openai.com/v1/chat/completions"
Private Const API_MODEL As String = "gpt-4o"

' データ構造
Private Type ClauseInfo
    番号 As Long
    見出し As String
    本文 As String
    種類 As String
    リスクレベル As String
    問題点 As String
    修正案 As String
    根拠 As String
End Type

Private Type ReviewResult
    契約書名 As String
    総条項数 As Long
    高リスク数 As Long
    中リスク数 As Long
    低リスク数 As Long
    条項情報() As ClauseInfo
End Type

' ======================================
' メイン処理
' ======================================

' 契約書レビューシステム(メイン)
Sub 契約書自動レビューシステム()
    Dim filePath As String
    Dim result As ReviewResult
    Dim startTime As Double
    
    startTime = Timer
    
    ' ファイル選択
    filePath = Application.GetOpenFilename( _
        "Word文書 (*.docx),*.docx,すべてのファイル (*.*),*.*", _
        , "レビューする契約書を選択してください")
    
    If filePath = "False" Then Exit Sub
    
    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
    
    ' 処理開始
    MsgBox "契約書レビューを開始します。" & vbCrLf & _
           "処理には数分かかる場合があります。", vbInformation
    
    ' 1. 契約書を読み込み、条項ごとに分割
    result = ProcessContract(filePath)
    
    ' 2. Wordファイルにコメントを挿入
    Call InsertCommentsToWord(filePath, result)
    
    ' 3. Excelにダッシュボードを作成
    Call CreateDashboard(result)
    
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    
    ' 完了通知
    MsgBox "レビュー完了!" & vbCrLf & _
           "総条項数: " & result.総条項数 & vbCrLf & _
           "高リスク: " & result.高リスク数 & "件" & vbCrLf & _
           "中リスク: " & result.中リスク数 & "件" & vbCrLf & _
           "低リスク: " & result.低リスク数 & "件" & vbCrLf & _
           "処理時間: " & Round(Timer - startTime, 0) & "秒", vbInformation
End Sub

' ======================================
' 契約書処理
' ======================================

' 契約書を処理
Private Function ProcessContract(filePath As String) As ReviewResult
    Dim wordApp As Object
    Dim doc As Object
    Dim clauses() As ClauseInfo
    Dim clauseCount As Long
    Dim i As Long
    Dim result As ReviewResult
    
    ' Word起動
    On Error Resume Next
    Set wordApp = GetObject(, "Word.Application")
    If wordApp Is Nothing Then
        Set wordApp = CreateObject("Word.Application")
    End If
    On Error GoTo 0
    
    wordApp.Visible = False
    Set doc = wordApp.Documents.Open(filePath, ReadOnly:=True)
    
    ' 条項を抽出
    clauses = ExtractClauses(doc)
    clauseCount = UBound(clauses) - LBound(clauses) + 1
    
    ' 各条項をAIでレビュー
    For i = LBound(clauses) To UBound(clauses)
        Application.StatusBar = "レビュー中... (" & i - LBound(clauses) + 1 & "/" & clauseCount & ")"
        
        ' 4段階レビュー
        Call ReviewClause(clauses(i))
        
        DoEvents
    Next i
    
    ' 結果を集計
    result.契約書名 = doc.Name
    result.総条項数 = clauseCount
    result.条項情報 = clauses
    
    For i = LBound(clauses) To UBound(clauses)
        Select Case clauses(i).リスクレベル
            Case "高"
                result.高リスク数 = result.高リスク数 + 1
            Case "中"
                result.中リスク数 = result.中リスク数 + 1
            Case "低"
                result.低リスク数 = result.低リスク数 + 1
        End Select
    Next i
    
    doc.Close False
    Application.StatusBar = False
    
    ProcessContract = result
End Function

' 契約書から条項を抽出
Private Function ExtractClauses(doc As Object) As ClauseInfo()
    Dim clauses() As ClauseInfo
    Dim para As Object
    Dim clauseCount As Long
    Dim currentClause As String
    Dim currentHeading As String
    Dim i As Long
    
    clauseCount = 0
    ReDim clauses(1 To 100)
    
    ' 段落を走査
    For Each para In doc.Paragraphs
        Dim paraText As String
        paraText = Trim(para.Range.Text)
        
        If Len(paraText) > 0 Then
            ' 見出しスタイルの場合
            If InStr(para.Style, "見出し") > 0 Or _
               para.OutlineLevel <= 3 Then
                
                ' 前の条項を保存
                If Len(currentClause) > 0 Then
                    clauseCount = clauseCount + 1
                    If clauseCount > UBound(clauses) Then
                        ReDim Preserve clauses(1 To UBound(clauses) + 50)
                    End If
                    
                    clauses(clauseCount).番号 = clauseCount
                    clauses(clauseCount).見出し = currentHeading
                    clauses(clauseCount).本文 = currentClause
                End If
                
                ' 新しい条項開始
                currentHeading = paraText
                currentClause = ""
            Else
                ' 本文を追加
                currentClause = currentClause & paraText & vbCrLf
            End If
        End If
    Next para
    
    ' 最後の条項を保存
    If Len(currentClause) > 0 Then
        clauseCount = clauseCount + 1
        clauses(clauseCount).番号 = clauseCount
        clauses(clauseCount).見出し = currentHeading
        clauses(clauseCount).本文 = currentClause
    End If
    
    ' 配列サイズを調整
    ReDim Preserve clauses(1 To clauseCount)
    
    ExtractClauses = clauses
End Function

' 1つの条項をAIでレビュー(4段階)
Private Sub ReviewClause(ByRef clause As ClauseInfo)
    Dim prompt As String
    Dim response As String
    
    ' 第1段階:条項の種類を識別
    prompt = "以下の契約書の条項について、その種類を識別してください。" & vbLf & vbLf
    prompt = prompt & "【条項】" & vbLf
    prompt = prompt & "見出し: " & clause.見出し & vbLf
    prompt = prompt & "本文: " & clause.本文 & vbLf & vbLf
    prompt = prompt & "以下のいずれかに分類してください" & vbLf
    prompt = prompt & "- 秘密保持条項" & vbLf
    prompt = prompt & "- 支払条件" & vbLf
    prompt = prompt & "- 納期・納品条件" & vbLf
    prompt = prompt & "- 解約条件" & vbLf
    prompt = prompt & "- 損害賠償・免責条項" & vbLf
    prompt = prompt & "- 知的財産権" & vbLf
    prompt = prompt & "- 一般条項(準拠法、管轄裁判所等)" & vbLf
    prompt = prompt & "- その他" & vbLf & vbLf
    prompt = prompt & "【回答形式】種類名のみ回答してください。"
    
    response = CallOpenAI(prompt, 0.1)
    clause.種類 = Trim(response)
    
    ' 第2~4段階:リスク評価、問題点、修正案を一度に取得
    prompt = "以下の契約書の条項について、法的リスクを評価し、問題点を指摘し、修正案を提案してください。" & vbLf & vbLf
    prompt = prompt & "【条項】" & vbLf
    prompt = prompt & "種類: " & clause.種類 & vbLf
    prompt = prompt & "見出し: " & clause.見出し & vbLf
    prompt = prompt & "本文: " & clause.本文 & vbLf & vbLf
    prompt = prompt & "【評価観点】" & vbLf
    prompt = prompt & "1. 一方的に不利な内容になっていないか" & vbLf
    prompt = prompt & "2. 曖昧な表現で解釈の余地がないか" & vbLf
    prompt = prompt & "3. 必要な条件が欠落していないか" & vbLf
    prompt = prompt & "4. 過度に厳しい条件になっていないか" & vbLf & vbLf
    prompt = prompt & "【重要】以下のJSON形式で必ず出力してください" & vbLf
    prompt = prompt & "{" & vbLf
    prompt = prompt & "  ""リスクレベル"": """" または """" または """"," & vbLf
    prompt = prompt & "  ""問題点"": ""具体的な問題点の説明""," & vbLf
    prompt = prompt & "  ""修正案"": ""具体的な修正文""," & vbLf
    prompt = prompt & "  ""根拠"": ""リスク判定の根拠""" & vbLf
    prompt = prompt & "}"
    
    response = CallOpenAI(prompt, 0.3)
    
    ' JSON解析
    clause.リスクレベル = ExtractJsonField(response, "リスクレベル")
    clause.問題点 = ExtractJsonField(response, "問題点")
    clause.修正案 = ExtractJsonField(response, "修正案")
    clause.根拠 = ExtractJsonField(response, "根拠")
    
    ' デフォルト値設定
    If clause.リスクレベル = "" Then clause.リスクレベル = "低"
    If clause.問題点 = "" Then clause.問題点 = "特に問題は検出されませんでした。"
    If clause.修正案 = "" Then clause.修正案 = "修正不要"
End Sub

' ======================================
' OpenAI API
' ======================================

' OpenAI APIを呼び出し
Private Function CallOpenAI(prompt As String, temperature As Double) As String
    Dim http As Object
    Dim jsonRequest As String
    Dim response As String
    
    Set http = CreateObject("MSXML2.XMLHTTP")
    
    jsonRequest = "{" & _
        """model"":""" & API_MODEL & """," & _
        """messages"":[{""role"":""user"",""content"":" & JsonEncode(prompt) & "}]," & _
        """temperature"":" & temperature & _
    "}"
    
    On Error GoTo ErrorHandler
    
    http.Open "POST", API_ENDPOINT, False
    http.setRequestHeader "Content-Type", "application/json"
    http.setRequestHeader "Authorization", "Bearer " & API_KEY
    http.send jsonRequest
    
    If http.Status = 200 Then
        response = http.responseText
        
        Dim contentStart As Long
        Dim contentEnd As Long
        contentStart = InStr(response, """content"":""")
        
        If contentStart > 0 Then
            contentStart = contentStart + 11
            contentEnd = InStr(contentStart, response, """")
            response = Mid(response, contentStart, contentEnd - contentStart)
            response = Replace(response, "\n", vbCrLf)
            response = Replace(response, "\""", """")
        End If
        
        CallOpenAI = response
    Else
        CallOpenAI = "[API Error: " & http.Status & "]"
    End If
    
    Exit Function
    
ErrorHandler:
    CallOpenAI = "[API Error: " & Err.Description & "]"
End Function

' JSON文字列をエンコード
Private Function JsonEncode(text As String) As String
    Dim result As String
    result = text
    result = Replace(result, "\", "\\")
    result = Replace(result, """", "\""")
    result = Replace(result, vbCrLf, "\n")
    result = Replace(result, vbCr, "\n")
    result = Replace(result, vbLf, "\n")
    JsonEncode = """" & result & """"
End Function

' JSONフィールドを抽出
Private Function ExtractJsonField(jsonText As String, fieldName As String) As String
    Dim startPos As Long
    Dim endPos As Long
    Dim searchStr As String
    Dim value As String
    Dim i As Long
    Dim char As String
    Dim prevChar As String
    
    searchStr = """" & fieldName & """:"
    startPos = InStr(jsonText, searchStr)
    
    If startPos = 0 Then
        ExtractJsonField = ""
        Exit Function
    End If
    
    startPos = startPos + Len(searchStr)
    
    Do While startPos <= Len(jsonText)
        char = Mid(jsonText, startPos, 1)
        If char = """" Then
            startPos = startPos + 1
            Exit Do
        ElseIf char <> " " Then
            Exit Do
        End If
        startPos = startPos + 1
    Loop
    
    i = startPos
    Do While i <= Len(jsonText)
        char = Mid(jsonText, i, 1)
        prevChar = Mid(jsonText, i - 1, 1)
        
        If (char = """" And prevChar <> "\") Or char = "," Or char = "}" Then
            endPos = i
            Exit Do
        End If
        i = i + 1
    Loop
    
    If endPos = 0 Then
        ExtractJsonField = ""
        Exit Function
    End If
    
    value = Mid(jsonText, startPos, endPos - startPos)
    value = Replace(value, """", "")
    value = Replace(value, ",", "")
    value = Replace(value, "}", "")
    value = Trim(value)
    value = Replace(value, "\n", vbCrLf)
    value = Replace(value, "\""", """")
    
    ExtractJsonField = value
End Function

' ======================================
' Word文書へのコメント挿入
' ======================================

' Wordファイルにコメントを挿入
Private Sub InsertCommentsToWord(filePath As String, result As ReviewResult)
    Dim wordApp As Object
    Dim doc As Object
    Dim para As Object
    Dim i As Long
    Dim clauseIndex As Long
    Dim commentText As String
    Dim colorCode As Long
    Dim outputPath As String
    
    ' Word起動
    On Error Resume Next
    Set wordApp = GetObject(, "Word.Application")
    If wordApp Is Nothing Then
        Set wordApp = CreateObject("Word.Application")
    End If
    On Error GoTo 0
    
    wordApp.Visible = True
    Set doc = wordApp.Documents.Open(filePath)
    
    clauseIndex = 1
    
    ' 段落を走査してコメント挿入
    For Each para In doc.Paragraphs
        If clauseIndex > result.総条項数 Then Exit For
        
        ' 見出しを検索
        If InStr(para.Range.Text, result.条項情報(clauseIndex).見出し) > 0 Then
            
            ' リスクレベルに応じて色を設定
            Select Case result.条項情報(clauseIndex).リスクレベル
                Case "高"
                    colorCode = RGB(255, 200, 200) ' 赤系
                Case "中"
                    colorCode = RGB(255, 255, 200) ' 黄系
                Case "低"
                    colorCode = RGB(200, 255, 200) ' 緑系
                Case Else
                    colorCode = RGB(255, 255, 255) ' 白
            End Select
            
            ' 背景色を設定
            para.Range.HighlightColorIndex = GetHighlightColor(result.条項情報(clauseIndex).リスクレベル)
            
            ' コメントテキスト作成
            commentText = "【" & result.条項情報(clauseIndex).種類 & "】" & vbCrLf & vbCrLf
            commentText = commentText & "リスクレベル: " & result.条項情報(clauseIndex).リスクレベル & vbCrLf & vbCrLf
            commentText = commentText & "【問題点】" & vbCrLf
            commentText = commentText & result.条項情報(clauseIndex).問題点 & vbCrLf & vbCrLf
            commentText = commentText & "【修正案】" & vbCrLf
            commentText = commentText & result.条項情報(clauseIndex).修正案 & vbCrLf & vbCrLf
            commentText = commentText & "【根拠】" & vbCrLf
            commentText = commentText & result.条項情報(clauseIndex).根拠
            
            ' コメント追加
            doc.Comments.Add para.Range, commentText
            
            clauseIndex = clauseIndex + 1
        End If
    Next para
    
    ' レビュー済みファイルとして保存
    outputPath = Replace(filePath, ".docx", "_レビュー済み.docx")
    doc.SaveAs2 outputPath
    
    MsgBox "レビュー結果をWord文書に挿入しました。" & vbCrLf & _
           outputPath, vbInformation
End Sub

' ハイライト色を取得
Private Function GetHighlightColor(riskLevel As String) As Long
    Select Case riskLevel
        Case "高"
            GetHighlightColor = 6  ' 赤
        Case "中"
            GetHighlightColor = 7  ' 黄
        Case "低"
            GetHighlightColor = 4  ' 緑
        Case Else
            GetHighlightColor = 0  ' なし
    End Select
End Function

' ======================================
' Excelダッシュボード作成
' ======================================

' ダッシュボードを作成
Private Sub CreateDashboard(result As ReviewResult)
    Dim ws As Worksheet
    Dim i As Long
    
    ' 既存のダッシュボードシートを削除
    On Error Resume Next
    Application.DisplayAlerts = False
    ThisWorkbook.Sheets("契約書レビューダッシュボード").Delete
    Application.DisplayAlerts = True
    On Error GoTo 0
    
    ' 新しいシートを作成
    Set ws = ThisWorkbook.Sheets.Add
    ws.Name = "契約書レビューダッシュボード"
    
    ' タイトル
    With ws
        .Range("A1").Value = "契約書レビューダッシュボード"
        .Range("A1").Font.Size = 16
        .Range("A1").Font.Bold = True
        
        .Range("A2").Value = "契約書名: " & result.契約書名
        .Range("A3").Value = "レビュー日時: " & Format(Now, "yyyy/mm/dd hh:mm:ss")
        
        ' サマリー
        .Range("A5").Value = "リスクサマリー"
        .Range("A5").Font.Bold = True
        .Range("A5").Font.Size = 12
        
        .Range("A6").Value = "総条項数"
        .Range("B6").Value = result.総条項数
        .Range("A7").Value = "高リスク"
        .Range("B7").Value = result.高リスク数
        .Range("B7").Interior.Color = RGB(255, 200, 200)
        .Range("A8").Value = "中リスク"
        .Range("B8").Value = result.中リスク数
        .Range("B8").Interior.Color = RGB(255, 255, 200)
        .Range("A9").Value = "低リスク"
        .Range("B9").Value = result.低リスク数
        .Range("B9").Interior.Color = RGB(200, 255, 200)
        
        ' 条項詳細
        .Range("A11").Value = "条項詳細"
        .Range("A11").Font.Bold = True
        .Range("A11").Font.Size = 12
        
        ' ヘッダー
        .Range("A12").Value = "No"
        .Range("B12").Value = "見出し"
        .Range("C12").Value = "種類"
        .Range("D12").Value = "リスク"
        .Range("E12").Value = "問題点"
        .Range("F12").Value = "修正案"
        
        .Range("A12:F12").Font.Bold = True
        .Range("A12:F12").Interior.Color = RGB(68, 114, 196)
        .Range("A12:F12").Font.Color = RGB(255, 255, 255)
        
        ' データ
        For i = 1 To result.総条項数
            .Cells(12 + i, 1).Value = result.条項情報(i).番号
            .Cells(12 + i, 2).Value = result.条項情報(i).見出し
            .Cells(12 + i, 3).Value = result.条項情報(i).種類
            .Cells(12 + i, 4).Value = result.条項情報(i).リスクレベル
            .Cells(12 + i, 5).Value = result.条項情報(i).問題点
            .Cells(12 + i, 6).Value = result.条項情報(i).修正案
            
            ' リスクレベルに応じて色分け
            Select Case result.条項情報(i).リスクレベル
                Case "高"
                    .Cells(12 + i, 4).Interior.Color = RGB(255, 200, 200)
                Case "中"
                    .Cells(12 + i, 4).Interior.Color = RGB(255, 255, 200)
                Case "低"
                    .Cells(12 + i, 4).Interior.Color = RGB(200, 255, 200)
            End Select
        Next i
        
        ' 列幅調整
        .Columns("A").ColumnWidth = 5
        .Columns("B").ColumnWidth = 30
        .Columns("C").ColumnWidth = 20
        .Columns("D").ColumnWidth = 10
        .Columns("E").ColumnWidth = 50
        .Columns("F").ColumnWidth = 50
        
        .Columns("E:F").WrapText = True
    End With
    
    ws.Activate
End Sub

' ======================================
' ユーティリティ
' ======================================

' 全Wordインスタンスを閉じる
Sub Word終了()
    Dim wordApp As Object
    
    On Error Resume Next
    Set wordApp = GetObject(, "Word.Application")
    If Not wordApp Is Nothing Then
        wordApp.Quit
        Set wordApp = Nothing
        MsgBox "Wordアプリケーションを終了しました。", vbInformation
    Else
        MsgBox "起動中のWordアプリケーションはありません。", vbInformation
    End If
    On Error GoTo 0
End Sub

2-5. 設定を変更

コードの上部にある設定値を変更します

9行目:OpenAI APIキー

Private Const API_KEY As String = "YOUR_OPENAI_API_KEY"

↓ 変更(自分が持っているAPIキーを入力する)

Private Const API_KEY As String = "sk-proj-あなたのAPIキー"

11行目:使用するAIモデル(オプション)

Private Const API_MODEL As String = "gpt-4o"

コスト重視の場合は以下に変更

Private Const API_MODEL As String = "gpt-4o-mini"

2-6. 保存

  1. Ctrl + S で保存

  2. VBAエディタを閉じる(Alt + Q


ステップ3:テスト用契約書の準備(5分)

3-1. サンプル契約書を作成

  1. Wordを開く

  2. 以下のテキストを入力(簡単なサンプル)

業務委託契約書

第1条(目的)
本契約は、甲が乙に対して、○○業務を委託し、乙がこれを受託することを目的とする。

第2条(委託業務の内容)
甲が乙に委託する業務の内容は、以下のとおりとする。
1. ○○に関する業務
2. ○○に関する業務

第3条(委託料)
甲は乙に対し、本件業務の対価として、金○○円を支払うものとする。
支払期日は、業務完了後30日以内とする。

第4条(秘密保持)
乙は、本契約の履行により知り得た甲の秘密情報を第三者に開示してはならない。
この義務は、本契約終了後も存続するものとする。

第5条(損害賠償)
乙の責に帰すべき事由により甲に損害が生じた場合、乙は甲に対し、
その全ての損害を賠償するものとする。

第6条(契約の解除)
甲は、いつでも本契約を解除することができるものとする。

第7条(準拠法)
本契約は日本法に準拠する。

第8条(合意管轄)
本契約に関する一切の紛争については、東京地方裁判所を専属的合意管轄裁判所とする。
  1. 名前を付けて保存
ファイル名:サンプル契約書.docx
保存場所:デスクトップ

ステップ4:レビュー実行(5分)

4-1. マクロを実行

  1. Excelファイル「契約書レビューシステム.xlsm」を開く

  2. Alt + F8 を押す

  3. マクロ一覧から「契約書自動レビューシステム」を選択

  4. 「実行」をクリック

4-2. ファイルを選択

  1. ファイル選択ダイアログが表示されます

  2. デスクトップの「サンプル契約書.docx」を選択

  3. 「開く」をクリック

4-3. 処理を待つ

処理中メッセージが表示されます

契約書レビューを開始します。
処理には数分かかる場合があります。

進行状況がステータスバーに表示されます

レビュー中... (1/8)
レビュー中... (2/8)
...

4-4. 結果を確認

完了メッセージ

レビュー完了
総条項数: 8
高リスク: 2件
中リスク: 3件
低リスク: 3件
処理時間: 約90秒

ステップ5:レビュー結果の確認(5分)

5-1. Word文書を確認

  1. 自動的にWordが開きます

  2. ファイル名:「サンプル契約書_レビュー済み.docx」

  3. 各条項に色付きハイライトとコメントが挿入されています

確認ポイント

  • 赤ハイライト:高リスク条項
  • 黄ハイライト:中リスク条項
  • 緑ハイライト:低リスク条項

各コメントには以下が含まれます

【損害賠償・免責条項】

リスクレベル: 高

【問題点】
「全ての損害」という表現が過度に広範で、
乙に一方的に不利な内容となっています。
上限額の設定や免責事由の明記がありません。

【修正案】
「乙の責に帰すべき事由により甲に損害が生じた場合、
乙は甲に対し、委託料の範囲内でその損害を賠償するものとする。
ただし、乙の故意または重過失による場合を除く。」

【根拠】
損害賠償責任は無制限ではなく、
契約金額の範囲内に制限することが一般的です。

5-2. Excelダッシュボードを確認

  1. Excelに戻る

  2. 「契約書レビューダッシュボード」シートが自動作成されています

  3. 以下の情報が表示されています

リスクサマリー

総条項数:    8
高リスク:    2 (赤背景)
中リスク:    3 (黄背景)
低リスク:    3 (緑背景)

条項詳細テーブル

No 見出し 種類 リスク 問題点 修正案
1 第1条(目的) 一般条項 特に問題は検出されませんでした 修正不要
2 第2条(委託業務の内容) 納期・納品条件 業務内容が「○○」と抽象的 具体的な業務内容を明記
5 第5条(損害賠償) 損害賠償・免責条項 無制限の損害賠償責任 上限額を設定
6 第6条(契約の解除) 解約条件 甲のみが一方的に解除可能 双方の解除権を明記

ステップ6:完成

これで実現できたこと

  • 契約書を数分で自動レビュー
  • 法的リスクを3段階で評価
  • 具体的な問題点を指摘
  • 修正案を自動生成
  • Word文書に直接コメント挿入
  • Excelでリスクを可視化

実際の契約書レビュー例

例1:高リスクと判定された条項

元の条項

第10条(知的財産権)
本業務の遂行により生じた一切の成果物に関する知的財産権は、
すべて甲に帰属するものとする。

AIレビュー結果

種類: 知的財産権
リスクレベル: 高

【問題点】
乙が既に保有している知的財産権や、
本業務と無関係に開発したツール・ノウハウまで
甲に帰属することになり、乙に著しく不利です。

【修正案】
「本業務の遂行により新たに創作された成果物に関する知的財産権は
甲に帰属する。ただし、乙が従前より保有する知的財産権、
および本業務と無関係に開発したツール・ノウハウについては、
この限りでない。」

【根拠】
既存の知的財産権まで譲渡することは過度な要求であり、
業務範囲を明確に限定すべきです。

例2:中リスクと判定された条項

元の条項

第8条(納期)
納期は令和○年○月○日とする。
やむを得ない事由により遅延する場合は、
甲乙協議の上、納期を変更することができる。

AIレビュー結果

種類: 納期・納品条件
リスクレベル: 中

【問題点】
「やむを得ない事由」の定義が曖昧で、
解釈の余地があります。また、協議が整わない場合の
取り扱いが明記されていません。

【修正案】
「納期は令和○年○月○日とする。
天災地変、法令の制定改廃、その他乙の責に帰すべからざる事由により
納期の遵守が困難となった場合、乙は速やかに甲に通知し、
甲乙協議の上、合理的な範囲で納期を延長することができる。」

【根拠】
不可抗力の範囲を明確化し、協議プロセスを具体化することで
紛争を予防できます。

例3:低リスクと判定された条項

元の条項

第12条(準拠法)
本契約は日本法に準拠するものとする。

AIレビュー結果

種類: 一般条項(準拠法、管轄裁判所等)
リスクレベル: 低

【問題点】
特に問題は検出されませんでした。
標準的な準拠法条項です。

【修正案】
修正不要

【根拠】
日本国内の取引において、日本法を準拠法とすることは
一般的かつ合理的です。

カスタマイズ例

カスタマイズ1:業種別ルールの追加

IT業界向けにカスタマイズ

' ReviewClause関数内のプロンプトに追加
prompt = prompt & "【IT業界特有のチェックポイント】" & vbLf
prompt = prompt & "- ソースコードの著作権帰属は明確か" & vbLf
prompt = prompt & "- SLA(サービスレベル)が具体的に定義されているか" & vbLf
prompt = prompt & "- データ保護・個人情報保護の条項があるか" & vbLf
prompt = prompt & "- セキュリティインシデント発生時の対応が明記されているか" & vbLf

建設業界向けにカスタマイズ

prompt = prompt & "【建設業界特有のチェックポイント】" & vbLf
prompt = prompt & "- 瑕疵担保責任の期間は適切か" & vbLf
prompt = prompt & "- 工期遅延時のペナルティは妥当か" & vbLf
prompt = prompt & "- 追加工事の取り扱いが明記されているか" & vbLf
prompt = prompt & "- 安全管理責任の所在は明確か" & vbLf

カスタマイズ2:リスクレベルの基準変更

より厳格な評価基準に変更

' ReviewClause関数内のプロンプトを修正
prompt = prompt & "【リスク評価基準】" & vbLf
prompt = prompt & "高リスク: 法的紛争に発展する可能性が高い、または金銭的損失が大きい" & vbLf
prompt = prompt & "中リスク: 解釈の余地があり、将来的にトラブルになる可能性がある" & vbLf
prompt = prompt & "低リスク: 標準的な内容で、特に問題なし" & vbLf

カスタマイズ3:過去契約書との比較(RAG実装)

過去の優良な契約書から類似条項を検索

' 新しい関数を追加
Private Function SearchSimilarClauses(clauseType As String, clauseText As String) As String
    Dim pastContracts As String
    Dim prompt As String
    Dim response As String
    
    ' 過去の契約書データベースを読み込み(Excel別シートに保存されている想定)
    pastContracts = LoadPastContracts(clauseType)
    
    prompt = "以下の契約書の条項について、過去の類似条項と比較し、" & vbLf
    prompt = prompt & "ベストプラクティスを提案してください。" & vbLf & vbLf
    prompt = prompt & "【現在の条項】" & vbLf
    prompt = prompt & clauseText & vbLf & vbLf
    prompt = prompt & "【過去の類似条項】" & vbLf
    prompt = prompt & pastContracts
    
    response = CallOpenAI(prompt, 0.3)
    SearchSimilarClauses = response
End Function

トラブルシューティング

エラー1:Wordが開かない

症状

実行時エラー'429': ActiveXコンポーネントはオブジェクトを作成できません

解決方法

  1. Wordを手動で一度起動して閉じる

  2. VBAから以下のコードを実行

Sub Word終了()

エラー2:APIエラー

症状

[API Error: 401]

解決方法

  1. APIキーを確認

  2. VBAコードに正しく設定されているか確認


エラー3:処理が遅い

症状

10ページの契約書で5分以上かかる

解決方法

  1. モデルを変更
Private Const API_MODEL As String = "gpt-4o-mini"
  1. 条項数が多い場合は、重要な条項のみレビュー対象にする

まとめ

実現できたこと

本記事で構築した契約書自動レビューシステムで実現できたこと

項目 従来(人間) システム導入後
レビュー時間 30分~1時間 1~3分
見落とし あり なし(AI が網羅的にチェック)
修正案 経験に依存 常に具体的な修正文を提示
一貫性 担当者により異なる 常に同じ基準で評価
コスト 法務担当者の人件費 API料金(1件10~30円)
利用可能時間 営業時間のみ 24時間いつでも

定量的効果

  • レビュー時間:30分 → 2分(93%削減)
  • 法務担当者の負荷:大幅軽減
  • 契約書品質:向上

定性的効果

  • 営業部門が自分でチェック可能
  • 法務部門は重要案件に集中できる
  • 見落としリスクの削減
  • 社内の法的リテラシー向上

3部作の総まとめ

記事 レベル 処理対象 実用レベル 主な学び
初級編 1 Excel単一セル 個人学習 API連携の基本
中級編 3 Word 50ファイル 部署レベル 大量処理、独自ルール
上級編 5 契約書レビュー 企業レベル 専門知識、多段階処理、実務応用

今後の発展

このシステムはさらに拡張可能です。

次のステップ

  • RAG実装:過去の契約書データベースから学習
  • 多言語対応:英文契約書のレビュー
  • PDFネイティブ対応:PDF契約書の直接処理
  • 承認ワークフロー:Teams/Slackと連携
  • 契約書テンプレート生成:AIで契約書ドラフトを自動作成

応用例

  • 就業規則のチェック
  • 利用規約のレビュー
  • 社内規程の整合性チェック
  • 法改正対応チェック

おわりに

長い記事でしたが、最後までお読みいただきありがとうございました。

契約書レビューという専門性の高い業務を、VBAと生成AIの組み合わせで自動化できることを示しました。

このシステムが、皆さんの業務効率化と法的リスク低減に役立つことを願っています。


参考資料


この記事が役に立ったら、ぜひ LGTM をお願いします

ご質問やフィードバックもお気軽にコメント欄へどうぞ

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?