リリースサイクルからのノート — 第8回(最終回)
第7回のリグレッションスイートをリリースします。動作します。スライス対応ゲートは実際のバグを捕捉します。較正済みジャッジは堅実に機能します。
その後、エンジニアリングリードから「すべてのPRで実行するコストはいくらか」と尋ねられます。掛け算してみます。PRあたり約12分のジャッジ推論、1日60件のPR、4次元 × 17スライス — 請求額は実費です。さらに悪いことに、開発者全員が1行のプロンプトのタイプミスでグリーンチェックを12分間待つことになります。ベロシティが落ち[1]、チームから不満が出て、誰かが「ゲートは夜間だけ実行すればよい」と提案します — それはまさに、ゲートが本来果たすべきすべてを放棄する方法です。
解決策はテストを減らすことではありません。解決策は層を分けてテストを行い、シグナルの大半を最初の90秒以内に届けることです。本稿では、ゲートスイートの下層で動作するものを扱います。サブ秒のコントラクトテスト、引き締まったスモーク層、コスト意識のあるフリート、そして新しいゲートが誰かをブロックする前の2週間のシャドウウィンドウです。
本稿は第8回、本シリーズの最終回です。読み終える頃には、4段階パイプラインから、すべてのコミットで実行されるコントラクトテストのフィクスチャまでの全体像が把握できます。
カスタム言語モデルにとってCIとは何を意味するのか?
カスタムLLMにとってのCIとは、ゲートスイートが繰り返す必要のない作業のことです。ゲートは意味的品質を採点します。CIは、ゲートが1つのジャッジトークンも消費する前に、ゲートのスコアを無意味にしてしまうあらゆる事柄を捕捉します。
コントラクトテストはミリ秒単位で実行され、プロンプトテンプレートが依然としてレンダリングされること、ツール呼び出しスキーマが依然としてパースされること、検索インデックスが依然として応答すること、マニフェストが依然として実在するハッシュを参照していることを検証します。これらは決定論的で、無料で、パイプラインの残りが存在を許される唯一の理由です。プロンプトテンプレートを壊すプルリクエストは、ジャッジ推論で12分かけて無意味な出力を採点した後ではなく、200ミリ秒で失敗すべきです。
コントラクト層は、PR量に対して線形にスケールするCIの請求額と、そうでないものとの違いを生みます。DivinciのCIランナーは、スキーマチェックで落ちていたはずのPRではなく、実際の意味的評価にジャッジ予算の90%超を費やします。その比率こそが、見出しの数字です。
従来のCIがLLMで破綻する理由 — コストの観点から
第1回と第7回では、生成モデルに対して決定論的CIが失敗する理由を扱いました。本稿で扱うのは、それら4つの特性のコストの側面であり、その存在自体ではありません。
| LLMの特性 | 従来CIの失敗 | コストの形状 |
|---|---|---|
| 非決定論的な出力 | 完全一致アサーションがフレーキーになる | 再実行がフレーキー率に応じて線形にコストを増幅する |
| 多次元的な品質 | 単一のブール値では情報が不足 | 各次元が別個の(有料の)ジャッジ呼び出し |
| プロバイダーのドリフト | ピン止めした gpt-4-2024-01-01 が静かに引退 | プロバイダーがチェックポイントを終息させると、再較正のバーストが発生 |
| 非局所的なプロンプト効果 | ローカル単体テストでは効果を捕捉不能 | 分布形状の変化はPR間で起き、PR内ではない — 差分ではなく全スイートの再実行が必要 |
CIアーキテクチャは、これらをそれぞれ手の届く価格で扱えるようにしなければなりません。コントラクトテストは特性1と3を安価に処理します。スモークテストは特性4を部分的に処理します。特性2を完全に処理できるのはフルスイートだけです — しかも実際に必要なPRに限ります。
CIのレイヤーケーキ — サブ秒から25分まで
私たちが出荷するアーキテクチャは4層構成で、各層は下位のより安価な層では捕捉できないものを捕らえることで、自らの計算コストを正当化します。全層のスライス対応の枠組みは、Tianpan氏のSemver Lieポストモーテムが明示した教訓に従っています[4]。集約シグナルは嘘をつき、スライスごとのシグナルが集約では見えないものを捕捉する、というものです。
コストの形状こそが設計です。PRの約74%はジャッジトークンを一切消費しません — コントラクトかスモークで十分です。フルスイートに到達するPRは、プロンプト、モデル設定、検索インデックス、または評価コードに触れたPRに限定されます — まさに、ゲートスイートだけが信頼に値するシグナルを提供する変更です。リリース候補は、第4層に到達する少数の割合を占めます。
コントラクトテスト — 不公平な優位性
コントラクトテストは最前線であり、最も安価な防衛線であり、「AI評価パイプライン」の威厳に値しないと感じて多くのチームが省略してしまう線でもあります。しかし当社の顧客スイートでは、ジャッジが呼び出される前に、リグレッション候補の30〜40%が実際にここで失敗しています。
コントラクト層は、以下の5つだけを検証し、それ以外は何も行いません。
- プロンプトテンプレートの描画。 すべてのテンプレートが、未束縛変数、暴走ループ、壊れたJinja風 include なしに、正規のフィクスチャに対して描画されること。
- ツール呼び出しスキーマ。 宣言された各ツールの引数スキーマがパース可能であること、JSONSchemaが有効であること、描画されたプロンプトが必須スロットを実際にすべて参照していること。
- マニフェスト整合性。 リリースマニフェスト内の全SHA — モデル、プロンプト、検索インデックス、ジャッジ、データセット — がレジストリに存在するアーティファクトに対応していること。ぶら下がりポインタは3層先ではなくここで失敗します。
- インデックス生存確認。 検索インデックスが既知のクエリに予算内で応答すること。検索を密かに壊した再構築済みインデックスは、本番ではなくここで表面化します。
- 拒否リストとトークンバジェット。 禁止トークンを導入したり、呼び出しあたりのトークンバジェットを超過したり、コンテキストウィンドウを超えて描画したりしたプロンプトテンプレートはここで失敗します。ヒューリスティックな意味類似度スコアリング[6]も十分安価なので、リテラル文字列マッチングでは不十分なファジー一致の拒否リストカバレッジ用にコントラクト層で実行可能です。
# 代表的なコントラクトテストの呼び出し例 — 約600 msで完了
divinci ci contract \
--manifest release/staging.yaml \
--check schema,template,manifest,index,denylist \
--fail-fast \
--json-out /tmp/contract-report.jsonこれらのいずれも、ジャッジを呼び出しません。いずれも非決定論的ではありません。いずれも測定可能な金額のコストはかかりません。そして、いずれも「ゲートスイートが医療スライスのリグレッションを報告した」というアラート — そもそもモデルが正しく生成できなかったはずの出力を採点するのに丸12分のジャッジ推論を浪費していたであろうアラート — の一群を丸ごと除外します。
スモーク層 — 90秒、PRあたり約$0.05
コントラクト層が安価で不公平な優位性なら、スモーク層はコーヒー1杯未満の値段で実際にリグレッションを捕捉する層です。最も処理量の多いスライスから抽出した20〜30ケースを、タスク完了と安全性のみで採点します。忠実性も、レイテンシも、検索接地チェックもありません。すべてのPRがこれを実行します。約90秒で完了するのは、ケースが構造化出力スキーマで単一のジャッジ呼び出しにバッチ処理されているためであり、ジャッジがリリース候補で使う最高品質のものではなく、安価な較正済みジャッジであるためです。
私たちはリグレッションログで、出荷された各修正をどの層が捕捉したかを追跡しており、顧客導入環境における過去6か月のヒストグラムは一貫しています。
流出する3%は、第5回の即時ロールバックが存在する理由です。ゲートは流出ゼロを約束するのではなく、厳しい上限と、通過してしまったものに対する迅速な復旧を約束します。
CIフリートのサイジング — 12分スイートを安価に保つ方法
フルスイート層は、計算が成立しなければならない場所です。素朴な実装はジャッジを「ケース × 次元」ごとに1回呼び出し、それらを逐次実行するため、請求額はケース数に対して線形にスケールします。3つの最適化が、これを手の届く範囲に保つ作業の大部分を担います。
埋め込みキャッシュ。 各ゴールデンデータセットケースの検索コンテキスト指紋はハッシュ化されます。ケースが変わっておらず検索インデックスも変わっていなければ、キャッシュされた埋め込みが有効と見なされ、検索ステップはスキップされます。最初の安定週以降のヒット率は、当社の顧客導入環境で一貫して90%超です。
ジャッジバッチング。 較正済みジャッジは構造化出力で呼び出され、1呼び出しあたり8〜16ケースをバッチ処理します。ジャッジのトークンあたりコストは変わりませんが、ケースあたりのオーバーヘッドは下がります。バッチ全体でシステムプロンプトが分散償却されるためです。安全なバッチングの閾値は、そのバッチサイズでのジャッジ自身の較正済み一致度[2]によって設定されます — これを毎週のジャッジ較正パス(第7回)で計測します。
ケース間でのKVキャッシュ再利用。 全呼び出しの先頭に同一のシステムプロンプトとツール定義が来るモデルでは、そのプレフィックスのKVキャッシュはケースごとではなくスイート実行ごとに1回計算されます[3]。オープンウェイト導入では簡単に実現できます。クローズドAPIモデルでは、プロバイダーのプレフィックスキャッシュ対応に依存します。
これらを組み合わせた効果により、フルスイートのコストは前述のレイヤーケーキ図に示した数値水準に収まります。正確な数字は社内情報ですが、公開できる主張は比率です。PRの約74%はジャッジ費用ゼロ、約22%は数セント、残りの4%が、私たちが知る限り最も信頼度の高いリリース前シグナルのために数ドルを費やします。
シャドウCI — チームを壊さずに有効化する方法
私たちが最も頻繁に目撃してきたチームの単一最大の過ちは、新しいゲートを初日に「オフ」から「ブロッキング」へと切り替えることです。閾値は昨日のデータで調整されており、誤検知率は不明で、ゲートが最初に発火したときチームには、それが本物か誤報かを較正する基準がありません。オンコールの評価エンジニアが呼び出され、ゲートが無効化され、信頼が失われ、プロジェクトは死にます。
解決策はシャドウCIです。新しいゲートを2週間ノンブロッキングで実行し、結果をすべてのPRのbotコメントとして投稿し、ブロッキングに切り替える前に毎週誤検知率をレビューします。Divinci CIランナーには、まさにこのための --shadow フラグがあります。PRコメントは最終的なブロッキング版と同じ見た目になります — 同じ差分表示、同じスライス別の内訳 — ただしマージをゲートしません。
divinci ci run --layer=full --shadow --duration=14d --report-as=bot-commentウィンドウ全体で誤検知率が持続的に5%未満になれば、切り替えます。そうでなければ、スライス別の閾値を引き締め、ジャッジを再較正し、再度シャドウします。いずれにせよ、チームは初日に発火する新しいゲートに不意打ちされることはありません。
実際に組み合わさるGitHub Actionsワークフロー
レイヤーケーキを既存のCIに組み込む部分は .github/workflows/llm-ci.yaml で動作します。各層は配線されており、安価な層は早期に失敗し、高価な層は必要なときにだけ実行されます — needs: のチェーンとパスフィルタートリガーがその作業を担います[5]。
name: LLM CI
on:
pull_request:
paths:
- 'prompts/**'
- 'config/models.yaml'
- 'eval/**'
- 'retrieval/**'
- 'manifests/**'
jobs:
contract:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v4
- run: divinci ci contract --manifest manifests/staging.yaml --fail-fast
smoke:
needs: contract
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- run: divinci ci run --layer=smoke --post-pr-comment
env:
DIVINCI_API_KEY: ${{ secrets.DIVINCI_API_KEY }}
full:
needs: smoke
if: contains(steps.changes.outputs.paths, 'prompts/') || contains(steps.changes.outputs.paths, 'config/models.yaml')
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- run: divinci ci run --layer=full --post-pr-comment --gate
env:
DIVINCI_API_KEY: ${{ secrets.DIVINCI_API_KEY }}注目すべき点は3つあります。各層は needs: でチェーンされているため、コントラクトが壊れているとスモークは実行されず、スモークが壊れているとフルは実行されません。full ジョブは、12分間の実行に値する変更にパスフィルターでマッチングされます — READMEのタイプミス修正ではゲートスイートはトリガーされません。--post-pr-comment フラグは、GitHubを離れずにスライス別差分を可視化するためのものです。
失敗PRのデバッグループ
「ゲートが発火した」のもう半分は「理由を見せろ」です。medical slice task-completion dropped 0.04 というリグレッションスイート出力は、原因となったケースなしには対処不能です。私たちはPRコメントに、スライス別の最悪差分5件を、元の入力、ベースライン出力、候補出力、ジャッジの推論トレースとともに表示します。デバッグループは数秒で済むべきもので、数分かかるものではありません。
# このPRでmedical-sliceゲートを発火させた最悪の5ケースを取得
divinci ci diffs --pr 1247 --slice medical --dimension task_completion --top 5これは第6回の7ステップツリーと同じ診断面を、CIフィードバックループに配線したものです。PRを開いたエンジニアは、ケースレベルの根拠をPR上で直接確認できます。別の評価ダッシュボードを開く必要はありません。
バージョン管理の規律 — プロンプト、データセット、ジャッジをコードとして扱う
プロンプトテンプレート、ゴールデンデータセット、ジャッジプロンプトはすべてリポジトリ内に存在し、リリースマニフェストでハッシュ固定されます。マニフェストは、スイートを特定の再現可能な状態に結びつける唯一のオブジェクトです。
# manifests/staging.yaml — すべてのCI実行はこれをハッシュ化する
release_id: rel-staging
model: { sha: 0c1f9…, weights: r2://models/custom-v7.2, open_weights: true }
prompt: { sha: c4a8e…, template: prompts/support/v3.4.j2 }
retrieval: { sha: b21f0…, index: r2://indices/kb-2026-04 }
judge: { sha: d8e21…, rubric: eval/rubrics/v7.yaml }
dataset: { sha: a90b1…, file: eval/datasets/golden-2026-04.jsonl }CI実行がスコアを投稿すると、そのスコアはマニフェストハッシュでタグ付けされます。スコアが動いたとき、「どの入力が動いたか」という問いには直接の答えがあります。マニフェストを差分比較し、発火した層がまずどの次元を見るべきかを教えてくれます。これは第1回の4段階パイプラインと第4回のvindexレシートが共に閉じるループです。マニフェストは、本シリーズの8回すべてが異なる枠組みで構築してきた監査プリミティブです。
本稿が解決しないこと
本シリーズの全稿で書いてきたのと同じ3つの正直な制約です。
- CIはスイートに存在しないものはテストしない。 レイヤーケーキがどれほど巧妙でも、捕捉できるリグレッションは、ゴールデンデータセット内のいずれかのケースが警告したであろうものに限られます。リプレイ層は挙動ドリフトに対してこれを緩和しますが、これまで見られなかった新規クエリは本番に現れるまで流出し続けます。本システムは本番モニタリングと組み合わせる必要があります。
- コスト数値はモデル価格設定により変動する。 本稿のすべてのコスト数値は、四半期ごとに変動するジャッジトークン料金、埋め込み料金、推論料金に依存します。比率 — 74% / 22% / 4%、31% / 27% / 28% / 11% / 3% — が荷重を支える主張であり、ドル金額はある時点での例示です。
- プロバイダー側のチェックポイント変更は依然として困難。 クローズドAPIプロバイダーが安定した名前の背後でモデルを密かに更新したとき、コントラクト層では捕捉できません。ゲートスイートだけが、しかも事後にしか捕捉できません。プロバイダーが対応している場所では明示的なチェックポイント識別子をピン止めし、チェックポイントが発表された日をフルスイート再ベースラインのトリガーイベントとして扱うことで緩和します。根本的な問題は防止できません。
シリーズのまとめ
本稿は全8回の第8回です。全体の弧:
- Divinci AIでLLM CI/CDパイプラインを構築する方法 — 以降のすべてが内側で動作してきた4段階パイプライン(登録/ゲート/ロール/観測)。
- カスタム言語モデルにおけるCI/CDリリース失敗の10事例 — 2026年の命名済み失敗モード、それぞれを捕捉すべき段階にマッピング。
- LLM向けQAおよびリリース管理の12機能 — 機能マトリクスと、代替策に対してDivinciを位置づける3キャンプのベン図。
- 規制対象分野におけるカスタムLMの検証とリリース — コンプライアンスのディープダイブ、規制当局から段階へのマッピング、vindexレシート。
- 即時ロールバック付き自動LLM CI/CDパイプライン — 運用層、自動化スペクトル、自動ロールバックレシート。
- 7ステップでカスタムLLMのQA失敗を診断する方法 — 診断意思決定ツリー。モデルが正解である頻度はおよそ7回に1回。
- 2026年におけるカスタムLLMの自動リグレッションテスト — スライス対応Spearmanゲート、較正済みジャッジ、クローズドループ本番トレースリプレイ。
- 本稿。 上記すべてをすべてのPRで現実的に運用可能にするCIインフラ。
各要素は組み合わさります。マニフェストは監査プリミティブ、ゲートは安全層、診断ツリーは復旧ループ、vindexレシートは外部アンカー、レイヤーケーキは全体を全コミットで運用可能にする手段です。あなたのカスタムLLMリリースプロセスにこれら5つが揃っていないなら、そのギャップこそが、これら8稿が扱ってきたものです。
FAQ
全コミットで実行できる最も安価なテストは何ですか?
プロンプトテンプレートの描画チェックです。ミリ秒単位で実行され、ジャッジは不要で、驚くべき割合の破損を捕捉し、測定可能なコストは一切かかりません。まだ実行していないなら、私たちが推奨を知る限り、最もROIの高いCIの一手です。
カスタムLLM CIパイプラインのコストはどの程度を見込むべきですか?
典型的なPRで数セント、リリース候補PRで数ドル程度です。比率はジャッジ価格と、PRのうちプロンプトやモデル設定に触れる割合に依存します。上記の4%というリリース候補比率は典型的です。プロンプトを頻繁に反復する製品では比率が上昇し、平均額もそれに応じて上がります。
フルスイートを全コミットで実行すべきですか?
いいえ。プロンプト、モデル設定、検索、または評価コードに触れるPRにパスフィルターを適用してください。その他の変更にはコントラクト + スモークで十分であり、READMEのタイプミスで12分待たされれば、スプリント1回でチームの信頼を失います。フルスイートは貴重です。変更が品質次元を動かしうる場合に使ってください。
全員を壊さずに新しいゲートを導入するには?
2週間のシャドウウィンドウ、ノンブロッキングで行います。シャドウ期間中に観測される誤検知率に基づいて閾値を調整してください。誤検知率が許容範囲(当社では5%)未満で持続したときにのみブロッキングへ切り替えます。それ以外の方法では、全員が無視することを学んでしまうゲートが出来上がります。
1つだけ追跡するなら、どの数値を追跡すべきですか?
本番到達前に捕捉された確認済みリグレッションの割合です。本稿のヒストグラムでは、成熟したDivinci導入環境でこの値は約97%です。流出する3%が即時ロールバックの存在理由です。97%がスイートの目的そのものです。
参考文献
- DORA / Google Cloud. "Accelerate State of DevOps — CI velocity, change-failure-rate and time-to-restore-service." 「PRあたり12分は遅すぎる」を意見ではなく弁明可能な主張にする業界横断ベースライン。
- Zheng et al. "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena." arXiv:2306.05685. スモーク層とフル層で使用するバッチサイズで、バッチ化されたLLM-as-judge呼び出しが較正を維持できるという実証的根拠 — 本稿のコスト数値が実現可能である理由。
- Pope et al. "Efficiently Scaling Transformer Inference." arXiv:2211.05102. CIフリートサイジングのセクションで引用したKVキャッシュ再利用とプレフィックス共有の手法。
- Pan, Tianpan. "The Semver Lie: how a minor LLM update broke production." 2026年4月29日。集約のみのリグレッションスイートにおける2026年の命名済み失敗モード。CIレイヤーケーキを最初から最後までスライス対応にしている理由。
- GitHub. "GitHub Actions — chaining jobs with `needs:` and conditional execution." 本稿の.yamlが組み立てに使うプリミティブ。
- Zhang et al. "BERTScore: Evaluating Text Generation with BERT." arXiv:1904.09675. より安価な層でLLM-as-judgeの代替として参照したヒューリスティックな意味類似度メトリック。ゲート時には使用しないが、大規模な禁止フレーズ検出のためコントラクト層で有用。
Ready to Build Your Custom AI Solution?
Discover how Divinci AI can help you implement RAG systems, automate quality assurance, and streamline your AI development process.
Get Started Today