AI機能をいきなり100%公開してませんか? 本番事故を減らす「0→1公開ゲート」5段階
更新日: 2026-07-21
新しいモデル、新しいプロンプト、新しいAIエージェント。テスト環境ではええ感じやったから、そのまま全ユーザーへ公開する。
これ、ちょっと怖くないですか。
普通のソフトウェアなら同じ入力にはだいたい同じ結果が返ります。でもAI機能は、文章として自然でも意味がズレたり、安全ルールを外したり、コストだけ跳ねたりします。だから結論はシンプルです。100%公開の前に、ユーザー影響0%の観測から始める。
この記事では、これを「0→1公開ゲート」と呼びます。ユーザー影響0%の観測から始め、証拠が揃うたびに責任範囲を段階的に広げるリリース設計です。
5段階の全体像
- Shadow: 現行版を返しながら候補版も裏で実行する
- Offline eval: 保存した代表ケースで品質と安全を測る
- Canary: ごく一部だけ候補版を返す
- Human gate: 拡大するか人間が承認する
- Rollback: 停止条件を超えたら現行版へ戻す
大事なのは、AIに全部決めさせないことです。AIには差分の分類、異常候補の抽出、レポートの下書きを任せる。人間は許容損失、合否ライン、公開範囲、停止を決める。ここは責任の設計なんです。
10分で動くShadow実行
最初の成功は、候補版をユーザーへ返さず、現行版との差分ログを1行得ることです。
from dataclasses import asdict, dataclass
import json
import time
@dataclass
class Run:
version: str
text: str
latency_ms: int
def timed(version, fn, prompt):
start = time.perf_counter()
text = fn(prompt)
return Run(version, text, int((time.perf_counter() - start) * 1000))
def shadow_call(prompt, current, candidate):
primary = timed("current", current, prompt)
shadow = timed("candidate", candidate, prompt)
print(json.dumps({
"type": "ai_shadow_compare",
"primary": asdict(primary),
"candidate": asdict(shadow),
"exact_match": primary.text == shadow.text,
}, ensure_ascii=False))
return primary.text
def current(prompt):
return "返品は購入後30日以内です"
def candidate(prompt):
return "返品期限は購入日から30日です"
print(shadow_call("返品期限は?", current, candidate))
ユーザーへ返るのはprimary.textだけです。候補版は観測専用。これがShadowです。ただし、裏で実行するだけで安全になるわけではありません。機密情報を候補モデルへ送ってよいか、副作用のあるツールを呼ばないかは先に人間が決めます。
候補版は、read-only(読むだけ) に限定してください。メール送信、課金、削除、公開、デプロイのような副作用を持つツールは接続しない。どうしても外部入力を使うなら、個人情報をマスクし、候補モデルへ送信してよいデータかを先に確認します。Shadowは「ユーザーに見えない」だけで、「データが外へ出ない」という意味ではないんです。
Offline evalは、保存した代表ケースで再現する
Shadowは実際の入力分布を観測できる反面、まだ来ていない重要ケースを待つ必要があります。そこで、過去の失敗や境界値を匿名化した代表ケース集を保存し、候補版へ毎回通します。これがoffline evalです。
たとえば「返品期限」「例外商品の扱い」「根拠が見つからない質問」「個人情報を含む質問」をそれぞれ数件ずつ用意します。合格条件は、単なる文章一致ではなく、業務ルール違反がないこと、根拠がないときに断定しないこと、個人情報を出力しないこと。AIにケース案を増やしてもらうのは便利ですが、何を失敗と呼ぶかは人間が決めます。
一致率だけを品質にしない
上のexact_matchはデモには便利ですが、意味品質の合否には使えません。「30日以内」と「購入日から30日」は文字列が違っても意味は近い。一方、滑らかな文章でも返品期限を90日と答えたら業務上は不正解です。
そこで、最低でも4つを別々に測ります。
- 品質: 代表ケースに対する正答率、根拠との整合、業務ルール違反
- 安全: 個人情報、禁止操作、プロンプトインジェクションへの耐性
- 性能: p50/p95レイテンシ、タイムアウト率
- コスト: 1リクエストあたりのトークン量や推論費
OpenTelemetryでは、メトリクスを実行時に捕捉する測定値、トレースを一つのリクエストが通る経路として扱います。比較ログにai.versionやrelease.stageのような低カーディナリティ属性を付けると、現行版と候補版を集計しやすくなります。生のユーザーIDやプロンプト全文を属性に入れるのは避けましょう。個人情報の問題だけでなく、属性の組み合わせが増え続けて集計コストが膨らみます。
停止条件は公開前に書く
公開してから「どこまで悪くなったら止めよう」と相談すると、止める判断はだいたい遅れます。先に機械が読める形へ落としておきます。
{
"quality": {"min_pass_rate": 0.95},
"safety": {"max_critical_violations": 0},
"performance": {"max_p95_latency_ms": 2500},
"cost": {"max_cost_increase_ratio": 1.20},
"decision": "any_failure_stops_expansion"
}
この数字は推奨値ではなく例です。医療、決済、社内FAQでは損失の形が違うので、人間が決めます。AIに「妥当な数字を決めて」と頼むのではなく、AIには過去ログを要約させ、人間が受容可能な損失から逆算するのが安全です。
上のしきい値は説明用のダミーです。そのまま本番へ採用せず、自分の代表ケースと許容損失から決めてください。
Canaryは小さく返して止められる状態
Shadowでユーザー影響は0%です。次に、候補版を一部へ返すCanaryへ進みます。たとえば1%→5%→25%のように段階を置けますが、これは例であって万能値ではありません。トラフィックが少なければ1%では何も分かりませんし、決済のような高リスク処理では1件の失敗でも重い。
各段で見る問いは3つだけです。
- 誰に見えるか
- 何を測るか
- 誰が止めるか
割合より重要なのは、拡大条件と停止条件が事前にあることです。
人間の承認をCIに置く
GitHub Actionsのenvironmentにはrequired reviewersなどの保護ルールがあります。候補版の評価レポートをartifactへ保存し、本番拡大ジョブをenvironment承認待ちにできます。
name: ai-release-gate
on:
workflow_dispatch:
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python scripts/evaluate_candidate.py --policy release-policy.json
- uses: actions/upload-artifact@v4
with:
name: ai-eval-report
path: reports/ai-eval.json
expand-canary:
needs: evaluate
runs-on: ubuntu-latest
environment: production-ai
steps:
- run: ./scripts/set-traffic.sh candidate 5
production-ai側でrequired reviewersを設定すれば、評価が通ったあとも人間の確認を挟めます。なお利用できる保護ルールはプランや公開/非公開リポジトリで条件が異なるため、公式ドキュメントを確認してください。このYAMLは構成例で、実環境では動作未確認です。
Rollbackは最後の章ではなく最初の条件
戻し方がない状態はCanaryではありません。単なる小規模な本番実験です。
KubernetesのDeploymentはrolling update、rollout status、rollout undoを提供します。AI機能でも、モデル名やプロンプト版を環境変数・設定ストア・feature flagで切り替えられるようにしておくと、アプリ全体の再実装なしで戻せます。
kubectl rollout status deployment/ai-api
kubectl rollout undo deployment/ai-api
kubectl rollout status deployment/ai-api
ロールバック後も終わりではありません。どの停止条件を超えたか、影響件数、候補版の識別子、復旧時刻を残す。これが次の公開を速くします。
AIに任せるプロンプト3本
1. Shadow差分の分類
現行版と候補版の出力差分を、表現差・事実差・業務ルール差・安全差に分類してください。
根拠が足りない場合は不明とし、合否は決めないでください。
人間が優先レビューすべき順と理由をJSONで返してください。
2. 停止条件の抜け漏れレビュー
このAI機能の停止条件案を、品質・安全・性能・コストの4軸でレビューしてください。
不足する観測値と、誤検知しやすい条件を列挙してください。
しきい値は決定せず、判断に必要な過去データだけ提案してください。
3. 公開判定レポートの下書き
添付した評価結果から、継続・停止・追加観測の3案を作ってください。
各案に根拠、未観測、最悪時の影響、戻し方を付けてください。
最終決定者が30秒で比較できる表にしてください。
反証: 5段階すべてが必要とは限らない
低リスクの社内文章補助で、出力を人間が毎回確認し、副作用もないなら、offline evalと手動rollbackだけで十分かもしれません。比較対象がない完全な新規機能ならShadowもできません。トラフィックが極端に少ないとCanaryの統計も安定しません。
見分け方は、失敗したとき「誰に、どれだけ、戻せる形で影響するか」です。影響が小さく可逆なら簡略化する。外部送信、決済、削除、公開、個人情報が絡むなら段階を増やす。仕組みは多いほど偉いのではなく、失敗の重さに釣り合っていることが大事なんです。
今日10分でやる4ステップ
- 現行関数と候補関数を
shadow_callで包む - ユーザーへ返す値が現行版だけか確認する
- ダミー入力を1件流して比較ログを1行得る
- 品質・安全・性能・コストから停止条件を1つだけ書く
まずは候補版を誰にも見せず、差分が見える状態にする。それだけで「祈って一括公開」から「証拠を見て段階公開」へ変わります。
参考リンク(一次情報・2026-07-21確認)
- Kubernetes: Update a Deployment Without Downtime
- GitHub Docs: Deployments and environments
- OpenTelemetry: Metrics
- OpenTelemetry: Traces
※Pythonのshadow_callは2026-07-21に実行・構文確認済みです。GitHub ActionsとKubernetesの例は公式仕様と照合しましたが、個別環境では動作未確認です。
生成AI活用エンジニア&3児のパパ。AI×開発の実践知を毎日発信しています → X