Kunavo(AI APIゲートウェイ)の運営チームです。コード・執筆・翻訳にAIを使用しています。この記事では、自作の小さなオフライン判断ロジックを10件のテストで確認しました。実サービスへの生成リクエスト、顧客ログ、音楽作品は使っていません。
非同期の音楽生成では、POSTがタイムアウトしても「生成されなかった」とは限りません。サーバー側では受付済みで、応答だけが届かなかった可能性があります。この状態で新しいキーを発行して再送すると、別の生成として扱われる可能性があります。
ここではPythonの標準ライブラリだけで、復旧時の「次に何をするか」を確認します。HTTPクライアントや永続キューの完成版ではありません。
1. 先に残すのは、結果ではなく依頼の記録
最初のPOSTより前に、次の情報を永続化します。
- 業務上の操作IDと利用アカウント・環境
- その生成依頼に割り当てた冪等キー
- 送信する本文と、そのハッシュ
- 最初の送信開始時刻
- 応答を受けた後のタスクID、状態、出力情報
API認証キーはこの記録に入れません。歌詞を含む本文は非公開情報になり得るため、保管先の権限と保存期間も決めます。
新しい曲を作るという判断と、同じ依頼の通信再試行を分けるのがポイントです。前者は新しい操作、後者は同じアカウント・キー・本文を維持します。
実装のprepare()はメモリ内の記録を作るだけです。実ワーカーでは、送信前にDB等への保存を完了させ、同一操作を複数ワーカーが同時実行しないようにしてください。
2. 「タスクIDがあるか」で復旧経路を分ける
| 状態 | 次の判断 |
|---|---|
| POSTの応答を失い、IDが不明 | 有限の復旧期間内で、元のキーと同じ本文を使用 |
| IDが判明している | そのIDをGETする。新規POSTはしない |
failed |
エラーを調べる。別の生成は明示的に決める |
completed、archived=false
|
一時URLのまま完了扱いにせず、保存処理を手配 |
| IDが不明なまま復旧期間を超過 | 照合・手動確認へ。自動でキーを更新しない |
| ローカルのポーリング回数上限 | IDを保持して待機を中断。サーバーの処理取消とは区別 |
Kunavoの公開仕様では、音楽の非同期受付はPOST /v1/audio/music/jobs、状態確認はGET /v1/audio/music/jobs/{id}です。新規受付はHTTP 202、冪等キーによる再取得は200を返します。どちらも本文の状態を確認します。
今回のロジックは、IDが分かっていれば再送期間を過ぎてもGETを選びます。POSTを再送できる期間と、既存タスクを照会する期間を同じ条件にしないためです。GETが404なら、新しく生成する前にアカウント、ID、結果の保存期間などを確認します。
3. 冪等キーを万能な保証と考えない
公開ドキュメントは再送期間をおおむね24時間と説明しています。一方、このサンプルはアプリ側の保守的な判断として1時間で不明な送信を照合対象にします。1時間はサーバーのTTLではありません。再試行のたびに開始時刻を更新すると上限が無意味になるため、最初の時刻を保持します。
if fingerprint(state["payload"]) != state["payload_sha256"]:
return {"action": "manual_review", "reason": "payload_changed"}
同じキーで本文だけを変える操作は、ローカル側で止めます。これは「サーバーが必ず本文の不一致を拒否する」という主張ではありません。
また、上流の受付とローカル記録の保存の間には障害が起き得ます。冪等キーがあるからといって、あらゆる障害下のexactly-once実行や重複課金防止を保証できるわけではありません。
4. ポーリングの停止は、生成の失敗ではない
サンプルの待ち時間は5、10、20、30秒、以後30秒です。12回の正常な待機応答を観測したらpause_pollingを返します。ここで元のIDを捨てて再生成してはいけません。停止したのはクライアントの待機であり、リモート処理は継続している可能性があります。
この12回は通信エラーを含めた総試行上限ではありません。実ワーカーにはHTTPタイムアウト、通信エラー用の別上限、jitter、Retry-Afterへの対応が必要です。401/403や入力検証エラーを無条件に再試行する設計にもしていません。
5. 生成完了と保存完了を分ける
completedでも、output.archivedと出力URLを確認します。archived=falseはCDN保存へのフォールバックを確認する必要がある状態です。一時的なURLであれば、自分の保存先へのコピーを手配します。
このコードはダウンロードしません。archive_outputsという次の作業を返します。record_manifestもメタデータを記録できるという意味で、ローカルバックアップ済み、楽曲の採用済み、公開権利の確認済みを意味しません。
Webhookを使う場合は、署名・鮮度の検証、イベントIDでの重複処理防止が別途必要です。今回の例はポーリングの判断だけを対象としています。
6. オフラインで失敗パターンを確認する
Python 3.9以上、外部パッケージとAPIキーは不要です。リポジトリのルートから実行します。
python3 music-job-recovery/recovery.py
python3 -m unittest discover -s music-job-recovery -p 'test_*.py' -v
確認結果は10件成功。応答を失った後もキーと本文が変わらないこと、既知IDはGETを選ぶこと、失敗時に自動で新規生成しないこと、本文の変更、別タスクの応答、不完全な出力、終端後の矛盾した応答も検査しています。
このテスト結果から分かるのは、ローカルの判断が想定どおりかという点です。サーバー側の冪等性、実際の請求、音質、サービスの安定性は未検証です。
参考資料
- Kunavo Music API仕様(2026-10-09確認)
- 再現用ソースと適用範囲
まずは「受付後に応答だけを失う」ケースを自分のワーカーで再現し、保存済みの同じ操作へ戻れるかを確かめると、通常の成功例だけでは見えない復旧経路を確認できます。