セキュリティ成熟度やIT統制の「評価スコア」を出すプロダクトを作るとき、最初に決めることがあります。
採点をLLMにやらせるか、やらせないか。
私は「やらせない」を選び、その代わりにAIを文章化だけに閉じ込めました。この記事は、その設計判断と、それを思想でなく実装として持つために何を書いたかの話です。
「決定論的」は、言うだけなら誰でも言える
「決定論的スコアリング」と書いてあるプロダクトは珍しくありません。ただ、その言葉が本当かどうかは、境界値の扱いが書かれているかでだいたい分かります。
私が書いたエンジンには、こういうルールが静的に置いてあります。
avg < 2.0 → L1 / top_priority
avg < 3.0 → L2 / top_priority
avg < 3.5 → L3 / needs_improvement
avg >= 3.5 → L4 / sufficient
そしてコード上に、こういうコメントを添えています。
<(strictly less than)を使用するため、2.0 ジャストは L2 になる
この1行が書けるかどうかが、決定論の実体だと思っています。
「AIが総合的に判定します」と言っている限り、平均がちょうど2.0だったときにどちらに倒れるかは、誰にも書けません。実行するたびに変わりうるし、モデルのバージョンが上がれば変わります。そして評価スコアの世界では、この「ちょうど境界」の1件が、いちばん揉めます。
設計原則を3つに絞った
エンジンの冒頭に、こう書いています。
スコア算出エンジン(決定論的・AIなし)
設計原則:
- 純粋関数のみ(副作用・外部I/Oなし)
- 同じ入力から必ず同じ出力(再現性・監査耐性)
- question メタデータを動的に参照し、question_id のハードコードなし
→ methodology_version が変わっても engine を変更しない
1つ目と2つ目は説明が要らないと思います。地味に効いているのは3つ目です。
設問が変わってもエンジンを触らない
採点基準は、いずれ必ず変わります。制度が変われば設問も配点も変わる。
そのとき question_id がエンジンにハードコードされていると、設問を1問足すたびにエンジンを改修することになります。改修すれば、過去のスコアが再現できるかどうかを毎回検証しなければなりません。
そこで、設問側にメタデータ(function=NIST CSF 2.0 の機能、itgc_domain=ITGC の領域)を持たせ、エンジンはそれを動的に参照する形にしました。
物差しの目盛りは差し替えられるが、測り方そのものは変わらない。 methodology_version が変わってもエンジンのコードは変わらない、という状態を作っています。
入力と出力
型で見るとこうなっています。
入力
{ question_id: string; maturity_level: 0 | 1 | 2 | 3 | 4 }[]
// + 設問メタ(function, itgc_domain)
出力①:NIST CSF 2.0 の6機能別スコア
{
function: 'GV' | 'ID' | 'PR' | 'DE' | 'RS' | 'RC';
percent: number; // 0〜100(小数点2桁)
confidence: 'normal' | 'low';
}
出力②:ITGC 5領域の現在地
{
domain: string;
current_level: 'L1' | 'L2' | 'L3' | 'L4';
readiness: 'sufficient' | 'needs_improvement' | 'top_priority';
}
confidence を出力に持たせているのは、設問数が少ない機能では、スコアの解像度が落ちるからです。同じ80%でも、根拠の厚みが違う。それを黙って同じ顔で出すのは誠実ではないので、値として持たせています。
設問配分は均等ではない ── そしてそれは設計判断
ITGC 5領域の設問数は、次のようになっています。
| 領域 | 設問数 |
|---|---|
| アクセス管理 | 3 |
| 資産管理 | 5 |
| 変更管理 | 1 |
| 運用管理 | 7 |
| 統制環境 | 4 |
変更管理に1問しか置いていません。
これは手を抜いたのではなく、共通20問をどこまで一般化できるかの線引きです。変更管理は、開発体制・リリース頻度・扱うシステムの性質によって、聞くべきことが大きく変わります。全社共通の設問で深く踏み込もうとすると、多くの回答者にとって当てはまらない設問が増えます。
代わりに、文脈が確定したときだけ補問を出す形にしました。IPO準備中という文脈が入力された場合のみ、変更管理の補問が2問追加され、合計22問になります。
- 補問1:変更の承認・テスト・本番反映の手順
- 補問2:作業者と承認者の分離
共通20問は土台、文脈依存の深掘りは補問。 20問という数字を守るために設問を薄めるのではなく、深さが要る領域だけを条件付きで足す、という構造にしています。
AIを、翻訳だけに閉じ込める
ここからが本題です。スコアは決定論で出しますが、レポートの文章はAIに書かせています。 数値の羅列を人が読める日本語にする作業は、LLMが得意な領域です。
問題は、AIに文章を書かせると、書きながら数値を作ってしまうことです。「約8割の項目が」「前回比で15%改善」——入力に無い数字が、文章の流れとして自然に混入します。
これを防ぐために、2段構えにしました。
1段目:型に数値フィールドを置かない
生成レイヤーの冒頭にこう書いています。
AI文章生成レイヤー(翻訳のみ・算出禁止)
型定義:AIが返せる出力形式(数値フィールドは含めない)
AIが返せる型に、そもそも数値の置き場がない。 構造として渡せないようにするのが1段目です。
2段目:本文に混入した数値を検出して、生成を中止する
型で防いでも、本文テキストの中に数字を書き込むことは止められません。 そこで、生成後にバリデーションを通しています。
入力スコアに存在しない % 数値が本文に現れたら、レポートを返さずに中止します。
[ReportGuard] AI出力に入力に存在しない%数値を検出: …
レポート生成を中止しました。入力スコアに含まれない数値の創作は禁止されています。
警告を出して続行する、にはしませんでした。
理由は単純で、このレポートは経営会議や監査法人との対話に持ち込まれるからです。そこに1つでも根拠のない数値が載ると、他の正しい数値まで信用を失います。 数値が1つ壊れているレポートは、レポート全体が壊れているのと同じ扱いになる。だから、部分的に壊れたものを返すより、返さないほうが安全だと判断しました。
「AIに数値を作らせない」は、思想として言うだけなら簡単です。 検出して止めるコードを書いて、はじめて実装になります。
越権しない、という設計制約
最後に、機能ではなく線引きの話を1つ。
このエンジンは、勝手に合否を評価しません。 出すのは現在地のスコアと、取り組む優先順位(top_priority / needs_improvement / sufficient)まで。「適合」「不適合」は出しません。それは監査法人や認証機関の領域です。
同じ理由で、対外的な表現でも「準拠」「認証」という語を使わず、「NIST CSF 2.0 / ITGC の観点」 と書いています。基準そのものを作ったのは私ではないので、そう書くしかありません。
これは謙遜ではなく設計制約です。判定を出すと、判定の正しさに責任が発生します。 そして判定の正しさは、再現性でも透明性でも守り切れません。できないことをできると言わないほうが、この領域では技術的にも持続します。
まとめ
- 「決定論的」を名乗るなら、境界値をどちらに倒すかまで書く。 揉めるのはいつも境界の1件。
- 設問メタを動的に参照し、
methodology_versionが変わってもエンジンを変えない。 - 設問数の偏りは隠さない。共通は土台、深さが要る領域は文脈付きの補問で足す。
- AIは翻訳だけ。型に数値フィールドを置かず、本文に混入した数値は検出して生成を中止する。
- 評価はしない。 現在地と優先順位までに留める。
同じ課題で設計している方の参考になれば幸いです。
この記事の設計は、私が開発しているIT統制の可視化サービス(SecuROI)の実装がベースです。