一覧に戻る
    「ハルシネーション対策」を実装する前に、設計変更で消えていた──Phase 0 検証で起きた3つのピボット
    開発ラボ
    PRこの記事には広告が含まれています

    「ハルシネーション対策」を実装する前に、設計変更で消えていた──Phase 0 検証で起きた3つのピボット

    17 分で読める

    前回の記事で、影響範囲調査レシピジェネレーターの「調査レシピ方式」に発想を転換した話を書いた。記事の最後で「次は Phase 0 の遡及テスト設計について書く」と予告していた。

    今回はその続きなのだが、実際にやってみたら「遡及テスト設計」の話をするつもりが、テストの途中で設計そのものを3回作り直していたという記録になった。

    そして最後には、Phase 1 で対応予定だった「ハルシネーション対策」の Issue を、一行もコードを書かずに close することになった。この記事はその顛末です。


    前提:このツールが何をするか

    簡単に振り返っておくと、影響範囲調査レシピジェネレーターは、PR の diff から「この変更で見落としやすい盲点」を検出し、調査の手がかりを生成する CLI です。

    プロトタイプの動作フローはこうなっている。

    Phase 0 の目的は、この流れが実運用に耐えるかを遡及テストで確かめることでした。想定していた評価軸は大きく3つ。

    • P0-1 捕捉率: 過去のインシデント PR に対して、該当パターンを検出できるか
    • P0-3 ノイズ率: 無関係な PR で「該当なし」を正しく返せるか
    • P0-4 事実性: 生成されたレシピが実在するコードやファイルを参照しているか

    最初は単純に、過去に影響範囲の見落としが起きた PR をいくつか流して、パターン検出と生成レシピの質を定量的に測ればいい、と考えていました。


    ピボット1:プロダクト固有の前提が混じっていた

    最初のテストは拍子抜けするほど順調だった。新旧テーブル同期パターンの PR を流したら、該当パターンをピンポイントで検出し、関連する過去インシデントも正しく紐付けてきた。

    プロンプト側のバグ(JSON 例のブレースが str.format のプレースホルダと誤認される)や、モデル名の取り違え(使っているデプロイ名が微妙にズレていた)などはあったものの、大筋では動いている。「悪くない」と思って次の PR に進もうとした、その時でした。

    生成されたレシピを読み直していて、ふと手が止まった。

    うーん、提示された影響範囲調査のやり方、正しいのか判断できないな……

    具体的に言うと、こんな感じだった。

    • 存在するか怪しいディレクトリへの言及
    • checksum などのユーティリティ実行コマンド → 実在するか確認できない
    • SQL 例のテーブル名が、実テーブルではなく「それっぽい雛形」
    • バッチが特定のフレームワークで書かれている前提の grep

    要するに、それっぽく具体的なのだけれど、LLM が「知っている形式」で手順を埋めているだけで、実在するかの保証がない。いわゆるハルシネーションでした。

    ここでいったん、「検証モード(生成したコマンドを実行して空振りを注記)」と「L1 grounding(リポジトリのファイルツリーをプロンプトに含める)」という2つの対応策を Issue に起票し、Phase 1 で対応する、としました。この Issue が後で効いてくる。

    ただ、この時点でもう一つ、もっと根本的な違和感があった。

    プレイブックを覗くと、特定のカラム名、特定のフラグ名、特定の内部テーブルの命名規則など、自社プロダクト固有のナレッジが滲み込んでいる。これだと、レシピを見てもそれが「汎用的に正しい手順」なのか「自社の文脈を当て推量で埋めただけ」なのか判別できない。Phase 0 の事実性指標を測ろうにも、判断軸そのものがブレる。

    「プロトタイプなのに固有ナレッジが混じっているのは辛い。ゼロベースで作り直したい」と Claude Code に相談した。

    返ってきたのは、8個の汎用パターン(スキーマ変更、フラグ固定化、二重データパス……)でした。一見もっともらしいんだけど、何か引っかかった。

    いいんですけど、既存のファイルの情報に引っ張られてない? ゼロベースで考えた?

    この問いに対する Claude Code の返しは、正直、少し刺さった。

    正直に言うと、引っ張られていました。
    既存カテゴリの言い換えが半分で、新規は半分だけ。「ゼロベース」と言いながら半分は既存の再ラベリングです。

    そこから出てきた新案が、「技術カテゴリ」ではなく「盲点の軸(認知軸)」で分類し直すというものでした。

    ID盲点問い
    B1呼び出し元の網羅この変更は誰から呼ばれている? 全呼出元は新しい挙動に耐えるか?
    B2永続状態への影響この変更は何を DB/キャッシュ/ファイル/キューに残すか?
    B3環境間の差分本番と dev で状態/設定が異なるとき、同じ挙動をするか?
    B4時系列上の一貫性デプロイ中・ロールバック時・移行期間中でも破綻しないか?
    B5並行性・冪等性同時に複数走ったら? 途中で失敗したら? 再実行したら?
    B6外部契約契約を保っている相手を全員把握できているか?
    B7失敗時の可観測性失敗が "静かに" 起きるようにならないか?

    DB / API / env という「技術の分類」ではなく、「レビュアーが見落としやすい認知の軸」をパターンにする。こう並べると、このツールが本来埋めたかった「認識の問題」に、ようやく語彙が追いついたという感覚がありました。前回の記事で書いた「調べようと思えば調べられたが、調べるべきだという認識がなかった」を直接問い直す形になっている。

    ついでに、インシデント事例も固有案件の ID から、「E-B1-01: デフォルト引数の反転による silent な挙動変更」のような匿名化された汎用的な失敗の原型(アーキタイプ)に差し替えました。実インシデントではないので「過去に発生した」という表現は禁じ、「盲点の典型例」として位置づける、という運用も一緒に整理した。

    ここで最初のピボットが入った。「パターン体系をゼロベースで作り直す」。


    ピボット2:詳細手順は重すぎる。チェックリストに変える

    B1-B7 の再構築が終わり、もう一度同じ PR でレシピを生成してみた。パターン検出の精度は上がり、PR 固有の識別子を使った具体的な手順が返ってくるようになった。技術的には改善された

    改善されたのだけれど、今度は別の違和感が湧いてきました。

    生成されたレシピは12ステップ。各ステップが2〜3行の解説付きで、合計すると読むのに5〜10分かかる。真面目にやれば調査としては網羅的でしょう。ただ、こんなに丁寧に書かれたものを、全員が PR ごとに実行してくれるだろうか?

    形骸化するときの典型は、「やることが正しすぎて重い」です。正論すぎるチェックリストはだいたい読み飛ばされる。これは前回の記事で「静的なチェックリストが形骸化する」と書いたのと同じ轍を、動的生成でも踏もうとしている気配がしました。せっかく動的にしたのに、内容が重すぎて同じ結果になってしまう。

    そこで、レシピのフォーマットそのものを変えることにした。

    項目旧(詳細手順)新(チェックリスト)
    長さ10〜12ステップ × 2〜3行4〜6項目 × 1行
    内容「X を実行し、Y を確認し、Z を比較せよ」「〜した / していない」の二値確認
    コマンド各ステップに必須1項目に1個だけ、任意で末尾に添える
    期待読了5〜10分1〜2分

    原則は3つに絞った。

    1. 4〜6項目に収める。10項目を超えるとレビュアーは消化しない
    2. 二値で答えられる形にする(「〜した」「〜していない」)
    3. PR 固有の識別子(ファイル名、関数名、カラム名)を含める

    この変更を入れて再実行したら、同じ PR に対するレシピが一気に軽くなった。「これなら読む」という手触りが、ようやく出てきた感じでした。

    ここで2つ目のピボット。「詳細手順 → チェックリスト」。


    ピボット3:パターン別に並べると同じ話を3回読まされる

    これで行けるかと思ったのだけれど、実案件で CI 経由で走らせてみたら、次の問題が出てきました。

    ある PR に対して、B6(外部契約)、B4(時系列一貫性)、B3(環境間差分)の3つの盲点が同時に検出された。それぞれのパターンごとにレシピが生成され、それが縦に3つ並んで PR コメントに投稿された。

    結果として、全チェック項目のうちおよそ半分が重複していた。

    同じ変更内容の説明が3回、同じチェック項目が複数回、微妙に言い回しを変えて登場する。読んでいる開発者からすると、「さっき読んだぞ」の連続です。

    ここでの判断で少し迷ったのが、どこで統合すべきかでした。

    最初に浮かんだのは「チェックリストだけ重複排除する」案。でも、よく見たら変更概要の説明盲点の言及も重複している。部分的な修正では追いつかない。そこで、レポート全体を再構成する方針に切り替えた。

    最終形はこうなっている。

    # 影響範囲調査レシピ
    
    ## この PR の変更概要
    (diff の要点を1回だけ記述)
    
    ## 検出された盲点
    - B6 外部契約: 〜
    - B4 時系列: 〜
    - B3 環境差: 〜
    
    ## チェックリスト
    (統合・重複排除された項目)

    実装としては、パターン別のレシピ生成は残したまま、最後に LLM への追加呼び出しを1回挟んで、全レシピを統合・重複排除させる構成にした。コストは +1 API call、およそ3,000トークン増。個別レシピは成果物として残すので、詳しく追いたいときは辿れる。

    ここで3つ目のピボット。「パターン別表示 → 集約レポート」。


    ハルシネーション対策の Issue が、実装せずに close になった

    ここまでの3ピボットを終えて、もう一度 Issue 一覧を見返した。

    Phase 1 で対応予定だった「ハルシネーション対策」Issue は、ピボット1の時点で起票したものでした。「検証モードの追加」と「L1 grounding への昇格」でハルシネーションを抑え込む、という内容。

    ところが当時の問題を整理し直してみると、こうなっていた。

    当時の問題現在の状況
    架空ディレクトリへの言及チェックリスト形式では1行で完結し、架空パスを展開する余地がほぼない
    架空ユーティリティの呼び出し汎用プレイブックから固有識別子を排除したため、LLM は diff 由来の識別子だけを使うようになった
    雛形テーブル名の混入プレイブック側からプロダクト固有命名を削除済み
    命名規則の想定不一致プロダクト固有ナレッジを全排除

    当時ハルシネーションに見えていたものの大部分は、設計変更の副作用で消えていた

    当時、私は「検証モード(生成コマンドを実際に実行して空振りを可視化)」と「L1 grounding(ファイルツリーをプロンプトに入れて実在ファイルのみ参照させる)」という実装策で解こうとしていた。どちらも実装コストは小さくなく、特に検証モードはサンドボックス実行環境が絡むので結構な工数になる。

    それが、以下の3つの変更だけで、大部分が解消されていた。

    • プレイブックから固有識別子を削除(ピボット1の副産物)
    • 出力をチェックリストに制約(ピボット2)
    • 「diff に現れる識別子だけを使え」「プレイブックを複写するな」というプロンプト制約

    結果として、該当 Issue は close しました。運用で再びハルシネーションが問題になったら再検討する、という形で棚上げ。

    実装する前に、実装が要らなくなっていた。これは今回、一番嬉しかった瞬間でした。


    Phase 0 の手触りと、次に向かう場所

    駆け足で振り返ると、この Phase 0 検証で起きたことはこうなる。

    • ピボット1: プロダクト固有 → 汎用認知軸(B1-B7)
    • ピボット2: 詳細手順 → チェックリスト
    • ピボット3: パターン別 → 集約レポート
    • 副産物: 「ハルシネーション対策」の Issue が実装前に陳腐化

    最初に想定していた「遡及テストで捕捉率を測って Phase 0 合格判定する」という綺麗な流れとは、だいぶ違う着地になりました。正直、途中で何度か「これ、進んでいるのか戻っているのかわからない」と感じる瞬間があった。

    ただ、振り返ると、今回手を動かしていて効いたのは「テストしながら設計を問い直し続ける」という姿勢だったように思います。特にピボット1で「ゼロベースで考えた?」と一度立ち止まったことが、その後の2つのピボットをまとめて引き出した感触がある。あれを聞かずに進めていたら、プロダクト固有ナレッジの上に詳細手順形式を積み上げて、ハルシネーション対策の実装まで突っ走っていたはずです。作業量は何倍にもなっていたでしょう。

    Phase 0 は、当初想定していた「数字を取る」という目的は半分しか果たしていません。捕捉率やノイズ率の定量測定はまだ十分にやれていない。ただ、設計レベルの粗が一通り洗い出され、レシピの形も落ち着いた。「数字を取れる設計にたどり着いた」、というのが今の手触りです。

    次は、今のチェックリスト形式が実運用でどう消化されるか。開発者が実際にチェックを付けてくれるか、それとも読み飛ばされるか。この「人間側の受容度」こそが、本来の Phase 0 で測りたかったものでしょう。

    一度設計を作り直して整理しきる、という遠回りがあってから、ようやく本題のテストが始められる。プロトタイプの手触りとは、だいたいこういうものなのかもしれない、と思っています。


    参考:Claude Code と協働する

    今回のピボットは、Claude Code に「ゼロベースで考えた?」と一度問い直したところから動き始めました。AI と協働しながら設計を作っていく進め方については、以下の書籍が参考になります。

    この記事は役に立ちましたか?

    Coffee cup

    この記事が、何かの整理につながったら

    コーヒー1杯分の応援をもらえると嬉しいです。

    ※ これは応援とは別の話ですが、

    同じようなテーマを自分の文脈で整理したい場合は、 (文章だけだと詰まりやすい人向けに) 思考整理の壁打ちという形で対話の時間も取っています。

    対話の時間について