3D間取り 生成失敗時のエラーハンドリング

done

セッション開始:2026/06/09 07:18

planning developing done

目的 / なぜ必要か

3D間取りの生成(ChatGPT)で、 モデルが画像を返さなかった場合の挙動を整理する。 従来は画像が得られないと throw new Error("image not generated") していたため、 生成ルートが 500 を返し、レコードは generationStartedAt だけが立った 「生成中」状態のまま残り、60秒後に自動で再試行され続ける(画面側も GenerationLoading が 35秒ごとに自動リロードを繰り返す)。これを終端化し、失敗を記録できるようにするのが本セッションの目的。

確定した仕様(インタビューで合意)

  • 失敗は一般ユーザーには見せない。失敗時は outputImageUri入力画像(間取り図)をセットし、一般ユーザーには通常の結果として表示する(=失敗を表に出さない)。 エラー文言(generationError)はシステム管理者のみが詳細画面で閲覧できる。
  • 再生成は不可。失敗時も generationEndedAt を立てて終端化する。 これにより無限ローディング/自動リロードループを止め、生成ルートの再実行条件 (generationEndedAt: null)にも合致しなくなる。
  • エラー文言が空のケースは当面許容response.output_text が空文字だと generationError も空になり、システム管理者にも「生成エラー」アコーディオンが表示されない場合があるが、 現時点では許容する。
  • 失敗時は画像生成数を記録しないGenerationLog は成功時のみ作成する。

2系統の失敗(重要)

今回の変更で失敗の扱いが2系統に分かれた。両者は終端化されるか否かが異なるため区別して理解する。

① ソフト失敗(画像が返らない)

  • レスポンスに image_generation_call の結果が無いケース。
  • { success: false, error: output_text } を返す。
  • ルートで generationEndedAtgenerationError を保存し、 outputImageUri に入力画像をセット。終端(再試行されない)
  • HTTP は 200 を返す。

② ハード失敗(例外)

  • アップロード失敗・ネットワークエラー等、try/catch で捕捉される例外。
  • generateImageWithChatGpt は従来どおり re-throw
  • ルートの catch で 500 を返す。generationEndedAt は立たず、 60秒後に自動再試行される(一過性エラーのリトライ)。

この非対称(ソフト=終端 / ハード=リトライ)は意図的。モデルが「生成できない」と判断したコンテンツ起因の失敗はリトライしても結果が変わりにくいため終端化し、 一過性の通信・基盤エラーはリトライに委ねる。

実装内容(影響範囲)

変更

apps/user/actions/solid-floor-plan/generate-image/lib/chatgpt/index.ts

戻り値を判別可能ユニオン Resultsuccess: true | false)へ変更。画像が得られない場合は throw せず { success: false, error: response.output_text, instructions } を返す。 デバッグ用 console.log(response) を削除。 例外(ハード失敗)の catch は従来どおり re-throw。

変更

apps/user/actions/solid-floor-plan/generate-image/index.ts

戻り値型を同じ Result ユニオンへ変更し、 generateImageWithChatGpt の結果をそのまま中継。

変更

apps/user/app/api/solid-floor-plans/[solidFloorPlanId]/generate/route.ts

!generateImageResult.success の分岐を追加。失敗時は instructionsoutputImageUri = 入力画像generationEndedAtgenerationError を保存し、 GenerationLog は作成せず 200 を返す。デバッグ用 console.log を削除。

変更

apps/user/app/(vertical)/v/solid-floor-plans/[solidFloorPlanId]/page.tsx

authUser.isSystemManager && solidFloorPlan.generationError のとき 「生成エラー」アコーディオン(読み取り専用 Textarea)を追加。一般ユーザーには表示されない。

スキーマ / マイグレーション

prisma/schema/solid-floor-plan.prisma / prisma/migrations/20260609062420_solid_floor_plans/

SolidFloorPlangenerationError String? @map("generation_error") を追加 (ALTER TABLE ... ADD COLUMN "generation_error" TEXT)。prisma client 再生成済み。

レビューでの指摘・残課題

🟡 仕様確認: 失敗時に outputImageUri へ入力画像を入れることで、 一般ユーザーには「入力と同じ画像が結果として出る/共有・ダウンロード・ホームステージング作成ボタンが出る」状態になる点を確認。 「失敗は一般ユーザーに見せない」方針として意図的と合意。

🟡 既知の限界(許容): response.output_text が空のとき generationError も空となり、 システム管理者にも「生成エラー」が表示されない。当面許容。将来、空のときはデフォルト文言を入れる余地あり。

🔵 軽微(未対応): Result 型が generate-image/index.tslib/chatgpt/index.ts に重複定義されている。構造的に互換なので動作上は問題ないが、共通化の余地。

🔵 既存(本セッション対象外): 生成ルートのレスポンスキーが refinement のまま(別機能からの流用と思われる)。今回は変更せず。

検証

apps/usertsc --noEmit を実行し、型エラー無し(EXIT=0)を確認。 成功分岐後の generateImageResult.imageUri 参照も判別可能ユニオンのナローイングで安全。