コンテンツ翻訳の耐障害性
Champollion のコンテンツ翻訳パイプライン(Markdown/MDX ドキュメント)は、障害を適切に処理するための多層耐障害性システムを採用しています。キーバリュー翻訳では各バッチが小さくリトライのコストも低いのに対し、コンテンツ翻訳は大きなプロンプトと長い出力を伴うため、一時的な問題だけでなく構造的な理由で失敗することがあります。
問題の背景
コンテンツ翻訳の障害モードは、キーバリュー翻訳とは根本的に異なります。
| 障害モード | キーバリュー | コンテンツ |
|---|---|---|
| レート制限(429) | よくある、一時的 | よくある、一時的 |
| タイムアウト | まれ(小さいバッチ) | よくある(長い出力) |
| 空のレスポンス | まれ | よくある(出力制限、フィルター) |
| 出力の途中切断 | 該当なし(JSON で検証済み) | 無音で発生する |
| コンテンツフィルター | 極めてまれ | 起こりうる(CLI ドキュメント、セキュリティドキュメント) |
| モデルの制限 | リトライで解決する | リトライでは解決しない |
重要な洞察:同じ失敗したリクエストをリトライすることは冗長性ではなく、頑固さです。 適切な耐障害性システムは、なぜ失敗したかを特定し、それに応じてアプローチを変えます。
アーキテクチャの概要
レイヤー 1:診断優先リトライ
どのようにリトライするかを決める前に、システムは API レスポンスを検査して何が失敗したかを把握します。
終了理由の分析
すべての LLM API は、生成されたテキストとともに finish_reason を返します。Champollion はこれを使って、インテリジェントなリトライ判断を行います。
finish_reason | 意味 | アクション |
|---|---|---|
stop + コンテンツあり | モデルが正常に完了した | ✅ 結果を受け入れる |
stop + 空 | モデルが何も生成しなかった | ⚠️ 同じリクエストをリトライ(一時的) |
length | 出力がトークン制限に達した | 🔶 ドキュメントを自動チャンク分割 |
content_filter | 安全フィルターが出力をブロックした | 🔴 ログに記録してスキップ(リトライは無効) |
null / 欠落 | 不正なレスポンス | ⚠️ 同じリクエストをリトライ(一時的) |
これにより、すべての失敗をバックオフリトライで同一に扱う現在のアプローチが置き換えられます。
リトライバジェット
一時的な障害に対する標準リトライバジェット:
| ラウンド | 試行回数 | タイムアウト | バックオフ |
|---|---|---|---|
| 標準 | 4(0→3) | 60秒 | 1秒 → 2秒 → 4秒 |
| エスカレーション | 4(0→3) | 120秒 | 1秒 → 2秒 → 4秒 |
| 合計 | 8 | — | 最悪の場合 約3.5分 |
ラウンド間には 10 秒のクールダウンを設け、一時的な問題が解消されるのを待ちます。
レイヤー 2:コンテンツチャンク分割
ドキュメントがサイズのしきい値を超えた場合、またはレイヤー 1 が出力の途中切断を検知した場合、システムはドキュメントを翻訳可能なサイズのチャンクに分割します。
詳細なチャンク分割の設定については、コンテキストロールオーバーを参照してください。主なポイントは以下のとおりです。
分割戦略
- 見出し境界 —
##と###は自然な翻訳単位の境界です。各セクションは独立した翻訳に十分な自己完結性を持っています。 - 段落へのフォールバック — 単一の見出しセクションがチャンクサイズを超える場合は、二重改行で分割します。
- 強制分割 — 極めて長い段落(例:テーブル)に対する最終手段です。文の境界で分割します。
チャンク間のコンテキスト
各チャンクは、前のチャンクの翻訳の最後の 2〜3 段落をコンテキストとして受け取ります。これにより以下を防ぎます。
- 用語のドリフト — モデルは前のチャンクで「tableau de bord」と呼んだ内容を参照できます
- 代名詞の解決 — 前のセクションの先行詞が引き継がれます
- 文体の一貫性 — チャンク 1 で確立されたトーンがチャンク N まで維持されます
自動チャンク分割のトリガー
| トリガー | 動作 |
|---|---|
contentChunkSize が設定に指定されている | そのサイズを超えるドキュメントを常にチャンク分割 |
finish_reason: "length" が返された | フォールバックとして自動チャンク分割(設定なしでも) |
| 入力が約 12KB を超える(自動検出) | 提案をログに記録するが、強制はしない |
レイヤー 3:モデルフォールバックチェーン
設定されたモデルが一時的にではなく、構造的に継続して失敗する場合、システムは代替モデルを試みます。モデルによってコンテキストウィンドウ、出力制限、安全フィルター、多言語対応の強みが異なります。
デフォルトのフォールバックチェーン
{
"contentFallbackChain": [
"google/gemini-2.5-flash",
"anthropic/claude-sonnet-4"
]
}
設定されたモデルが常に最初に試されます。フォールバックモデルは、すべてのリトライラウンド(標準+エスカレーション)が消費された後にのみ使用されます。
複数のアーキテクチャを使う理由
| シナリオ | プライマリモデルが失敗 | フォールバックモデルが成功 |
|---|---|---|
| ベトナム語の CLI ドキュメント | Gemini が空を返す | Claude は問題なく処理する |
| 安全フィルターにかかるコンテンツ | OpenAI がブロックする | Gemini は異なるフィルターしきい値を持つ |
| 長い構造化テーブル | モデル A が途中で切断する | モデル B はより大きな出力ウィンドウを持つ |
フォールバックの価値はアーキテクチャの多様性にあります。異なるモデルファミリーは異なる障害モードを持っています。あるモデルにとって構造的な失敗が、別のモデルにとっては些細な問題である場合があります。
適用範囲
モデルフォールバックはコンテンツ専用です。キーバリューバッチは小さく、構造的に失敗することはほとんどありません。そこにフォールバックの複雑さを加えることは過剰設計になります。
レイヤー 4:障害の記録
障害が発生した場合、システムは黙って処理を続けるのではなく、適切に追跡・報告します。
同期中
- 失敗した項目は進捗出力に
[FAIL]として表示されます - 各障害は具体的な理由(タイムアウト、空のレスポンス、コンテンツフィルター、途中切断)をログに記録します
- 完了した項目はマニフェストに即座に保存されます(インクリメンタル永続化)
同期後
障害のサマリーが最後に出力されます。
┌─ Content Translation Failures ─────────────────────────────────────┐
│ │
│ 2 of 24 content translations failed: │
│ │
│ ✗ docs/reference/cli.md → vi │
│ Reason: empty response after 8 attempts + 1 fallback model │
│ Models tried: google/gemini-3.1-pro-preview, gemini-2.5-flash │
│ │
│ ✗ docs/guides/troubleshooting.md → ar │
│ Reason: content_filter (no retry — blocked by safety filter) │
│ │
│ Re-run: npx champollion@latest sync │
│ (22 completed translations are cached and won't re-run) │
└─────────────────────────────────────────────────────────────────────┘
リトライマニフェスト
失敗したファイルは .champollion-retry.json に書き込まれます。
{
"failedAt": "2026-05-27T21:45:00Z",
"files": [
{
"source": "docs/reference/cli.md",
"locale": "vi",
"reason": "empty_response",
"attempts": 8,
"modelsTried": ["google/gemini-3.1-pro-preview", "google/gemini-2.5-flash"]
}
]
}
次回の sync 実行時には、これらのファイルのみが再処理されます。完了したファイルはコンテンツハッシュマニフェスト(.champollion-content.lock)によって保持されます。
終了コード
| コード | 意味 |
|---|---|
| 0 | すべての翻訳が成功した |
| 1 | 設定エラー、API キーの欠落など |
| 2 | 部分的な失敗 — 一部のコンテンツ翻訳が失敗した |
設定
{
"contentChunkSize": 4000,
"contentOverlap": 200,
"contentFallbackChain": [
"google/gemini-2.5-flash",
"anthropic/claude-sonnet-4"
]
}
| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
contentChunkSize | number | null | null | コンテンツチャンクあたりの最大トークン数。null = チャンク分割なし(途中切断時のみ自動チャンク分割) |
contentOverlap | number | 200 | コンテキスト継続性のためのコンテンツチャンク間のオーバーラップトークン数 |
contentFallbackChain | string[] | [] | 設定されたモデルが構造的に失敗した場合に試みるフォールバックモデル |
実装状況
| 機能 | 状態 |
|---|---|
| 診断優先リトライ(finish_reason の解析) | 🔲 計画中 |
| コンテンツチャンク分割(見出し/段落分割) | 🔲 計画中 |
| チャンク間のコンテキストロールオーバー | 🔲 計画中 |
| モデルフォールバックチェーン | 🔲 計画中 |
| 障害サマリーレポート | 🔲 計画中 |
| リトライマニフェスト(.champollion-retry.json) | 🔲 計画中 |
| 部分的な失敗に対する終了コード 2 | 🔲 計画中 |
| エスカレーションリトライ(タイムアウト延長) | ✅ 実装済み(v3.3.3) |
| 試行番号付きリトライメッセージ | ✅ 実装済み(v3.3.3) |
| コンテンツエラーの明示的な失敗通知 | ✅ 実装済み(v3.3.3) |
関連情報
- コンテキストロールオーバー — バッチの一貫性とコンテンツチャンク分割の設定
- 同期の仕組み — 同期パイプラインの全体像
- 翻訳メソッド — 利用可能なメソッドとその特性