コンテンツにスキップ

GEPA で契約書レビュー Agent を育てる — DSPy 5 部品の実装骨格

作成日: 2026-07-01 出典 / きっかけ: 申請レビュー Agent の DSPy 構造を「取引先ドラフト契約書を自社基準でレビューし、問題条項を指摘する法務レビュー Agent」に置き換えて、コードの骨格を端から端まで通した設計メモ。 芯(1行): GEPA は重みを一切触らず、法務の過去判断(データ)と名指しの feedback を燃料に、指示文だけを「自社の法務基準が染み込んだもの」に育てる。ただし燃料(正解データ)とハーネス(足場)が先で、プロンプト最適化は後。 関連: dspy_現場プロンプト改善ループ_metricと所有権境界_整理(ループ・metric・所有権の理論) / 法務経理ai_改善ループとガードレール_事例込み資料(他社事例+ガードレール) / 法務経理ai_芯_現場はsignal源_1枚整理(芯の1枚) / agentic_reference_architecture_評価ループ 元論文: GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning(Agrawal et al., ICLR 2026 Oral)— 要約は Zotero ノートに保存済み。


0. 要点(3行)

  • 申請レビューの application_text → judgment, remand_comment は、契約書チェックの contract_text → risk_level, issues にそのまま対応する。骨格は Signature / Module / Metric / Dataset / Optimizer の 5 部品
  • 契約書が申請レビューと決定的に違うのは、「判定が合ってるか」だけでは足りない点。「要修正」と当てても危険条項を見落とせば実務では0点。だから metric を2軸(判定一致+論点の取りこぼし)にし、feedback に「どの論点を見落としたか」を名指しで入れる。これが GEPA の reflection に効く。
  • 教科書どおりにいかない現実は3つ: ①正解データ(過去の法務判断の構造化)が最大のボトルネック、②プロンプト最適化の前にハーネス(条項分割・出力拘束・実在性チェック)を締める、③ハルシネーション不許容なので最終ゲートは人間の法務

1. Signature:入出力の定義(人間が書く枠組み)

import dspy

class ContractReview(dspy.Signature):
    """契約書をレビューし、リスク判定と指摘コメントを返す。"""
    contract_text: str = dspy.InputField(description="契約書ドラフトの本文")
    risk_level: str = dspy.OutputField(description="リスク判定:問題なし / 要修正 / 締結不可")
    issues: str = dspy.OutputField(description="問題条項の指摘と修正提案")

申請レビューの application_text → judgment, remand_comment が、そのまま contract_text → risk_level, issues に対応している。

2. Module:推論モジュール

reviewer = dspy.ChainOfThought(ContractReview)

3. Metric:採点基準(設計の勘所)

契約書チェックが申請レビューと違うのは「判定が合ってるか」だけでは足りない点。採点を 2 軸に分ける。

def contract_metric_with_feedback(example, pred, trace=None):
    # 軸1:リスク判定が一致しているか
    level_ok = (example.risk_level == pred.risk_level)

    # 軸2:指摘すべき条項をちゃんと拾えているか(見落としが致命的)
    expected_issues = set(example.issue_tags)         # 例:{"損害賠償無制限", "自動更新", "準拠法"}
    caught = set(pred.issues_tags) if hasattr(pred, "issues_tags") else set()
    missed = expected_issues - caught                  # 見落とした論点

    if level_ok and not missed:
        return dspy.Prediction(score=1.0, feedback="判定・指摘ともに基準どおり。")

    # 部分点:判定は当たったが見落としあり → 0.5、判定も外した → 0.0
    score = 0.5 if level_ok else 0.0

    return dspy.Prediction(
        score=score,
        feedback=(
            f"契約書:『{example.contract_text[:200]}…』\n"
            f"期待リスク判定:{example.risk_level} / 予測:{pred.risk_level}\n"
            f"見落とした論点:{missed if missed else 'なし'}\n"
            f"想定される指摘:『{example.issues}\n"
            f"実際の指摘:『{pred.issues}\n"
            "特に『見落とした論点』を確実に検知できるよう、"
            "チェック観点や条項のパターンを指示文に追加・修正してください。"
        )
    )

ポイントは、単に「合ってる/外れてる」ではなく 「どの論点を見落としたか」を feedback に名指しで入れること。これで reflection_lm が「損害賠償の無制限条項を見落とす失敗が多い → 指示文に『損害賠償上限の有無を必ず確認せよ』を追記しよう」と、具体的に育てられる。

4. データセット:正解つきの実例(燃料)

trainset = [
    dspy.Example(
        contract_text="…業務委託契約のドラフト本文…",
        risk_level="要修正",
        issue_tags=["損害賠償無制限", "自動更新"],
        issues="第12条の損害賠償が上限なし。第3条の自動更新条項に解約予告期間の記載なし。",
    ).with_inputs("contract_text"),
    # …過去に法務がレビューした契約書を数十件…
]

.with_inputs("contract_text") で「Agent に渡すのは本文だけ」と宣言。risk_levelissues(=過去に法務担当者が実際に下した判断)は答え合わせ用に取っておく。

5. Optimizer:GEPA で指示文を育てる

dspy.configure(lm=dspy.LM("openai/gpt-4.4-mini"))     # 推論用:安く速く
reflection_lm = dspy.LM("openai/gpt-4.4")             # 反省用:賢いモデル

optimizer = dspy.GEPA(
    metric=contract_metric_with_feedback,
    reflection_lm=reflection_lm,
    max_full_evals=20,
)
optimized_reviewer = optimizer.compile(reviewer, trainset=trainset, valset=valset)

回すと、素朴だった指示文が法務の過去判断と feedback を燃料に育つ。

  • 最初: 「契約書をレビューせよ」
  • 世代を経て: 「契約書をレビューせよ。特に以下を必ず確認:①損害賠償条項に上限額の定めがあるか(無制限は要修正)、②自動更新条項に解約予告期間の明記があるか、③準拠法・管轄裁判所が自社に不利でないか…

——自社の法務基準が染み込んだ指示文に育つ。モデルの重みは gpt-4.4-mini のまま変わらず、指示文だけが賢くなる。

※ 上記のモデル名は「推論用は安く速く / 反省用は賢く」を示す例示。実装時は現行の実在モデル ID に置き換える。


契約書チェック特有の、実務での注意

教科書どおりにいかない現実。財務・法務・コンプラの会社全体 AI 化にモロに刺さる部分。

① 正解データの用意が最大のボトルネック

GEPA は「正解つきの過去事例」がないと1ミリも動かない。契約書チェックの正解=法務担当者の判断だが、これがきれいに構造化されて残っている会社はまれ。過去のレビュー済み契約書と指摘履歴を dspy.Example に整える前処理が、実は本番作業の大半。数十件でも始められるが、質の高い「却下例・修正例」がどれだけ集まるかで効果が決まる。

② プロンプト最適化の前に「ハーネス」を整える

GEPA で指示文を磨く前に、足場を先に固める:

  • 契約書 PDF → 構造化テキスト(条項ごとに分割
  • 出力を JSON Schema / Pydantic で拘束risk_level が3値のどれか、issues が条項番号つき、を機械的に強制)
  • 「第12条」と言われたら本当にその条項が存在するかの 実在性チェック

ハーネス整備とプロンプト最適化はトレードオフではなく両輪。足場が締まっているほど GEPA が探索すべき範囲が狭まり、少ないデータで本質を学べる。契約書のように出力の正確性が命の領域では、ここを飛ばすと「それっぽいけど条項番号がデタラメ」な Agent が育つ。

③ ハルシネーションが許されない領域

間違った「問題なし」が事故に直結する。GEPA で精度を上げても、最終的には人間の法務がゲートに立つ設計(Agent は一次スクリーニング、確定判断は人間)が現実的。普段の checker gate / human gate の発想がそのまま活きる。


つながり(このメモの位置づけ)

深掘り候補(未着手)

  • 実際に動く最小サンプル(ダミー契約書データ込み)を1ファイルに。
  • ②のハーネス側、特に「条項の実在性チェック」と「Pydantic での出力拘束」の組み方。
  • 正解データが少ないときのブートストラップ戦略(合成データ生成含む)。

作成: 2026-07-01 / 最終更新: 2026-07-01