🏗️

「AIに丸投げ」では上手くいかない ― インフラ選定を構造化してからLLMに渡す設計パターン

に公開

はじめに

コードを書く作業は自動化が急速に進みました。Cursor、GitHub Copilot、Claude Code。もう「実装」がボトルネックになることは少ない。

一方で、そのコードをどこで動かすか――インフラの選定は、いまだに人間が主導でやっています。

「ChatGPT に聞けばいいじゃん」と思うかもしれません。僕もそう思ってやってみました。結論から言うと、フリーチャットでインフラ相談をすると、ほぼ確実に議論が発散します

この記事では、その「なぜ発散するか」を分析したうえで、構造化入力 → ルールベースのプレスコアリング → LLM 提案 というパターンで解決するツール「Pikinfra」の設計を紹介します。

なぜフリーチャットでインフラ相談すると発散するのか

ChatGPT や Claude にこう聞いてみます。

SaaS を作りたい。Next.js + PostgreSQL で、ユーザーは最初 1,000 人くらい。インフラ構成を提案して。

返ってくる回答は悪くない。Vercel + Supabase が候補に挙がり、スケール時は AWS ECS に移行、みたいな話が出てくる。

問題はそのです。

  • 「セキュリティ的にはどう?」→ WAF、暗号化、VPC の話が広がる
  • 「コストは?」→ 各サービスの料金体系の比較が始まる
  • 「チームに Docker 分かる人いないんだけど」→ マネージドサービス寄りに話が戻る
  • 「GDPR 対応は?」→ リージョン制約の話に発展

30 分後、最初より選択肢が増えている。

根本原因: 評価軸が暗黙的

フリーチャットの問題は、何を基準に判断するかが事前に合意されていないことです。

インフラ選定には最低でもこれだけの観点があります:

  • プロジェクトのフェーズ(PoC?本番?)
  • 技術スタック(何で書くか)
  • 規模(ユーザー数、リクエスト数、データ量)
  • セキュリティ・コンプライアンス要件
  • チームのインフラ経験値
  • 予算
  • 優先度(コスト vs スピード vs スケーラビリティ)

ユーザーはこの全部を最初に言語化できない。だから AI も「場合によります」を繰り返すしかなくなる。

設計パターン: 構造化入力 → プレスコアリング → LLM

この課題を 3 層で解決します。

[Layer 1] 構造化入力(ウィザード)
    質問を設計して、評価に必要な情報を漏れなく収集する

[Layer 2] ルールベースのプレスコアリング
    回答を数値化し、大まかな方向性(tier)を決定する

[Layer 3] LLM による具体的な構成提案
    方向性が固定された状態で、具体的なサービスを選定させる

LLM にすべてを任せるのではなく、判断の枠組みを先に作ってから LLM に渡す。これが核心です。

勘のいい方は気づいたかもしれませんが、これは spec 駆動開発と同じ構造 です。

  • TDD: テスト(spec)を書く → 実装する → spec を満たすか検証する
  • Pikinfra: 要件(spec)を収集する → スコアリングで正規化する → LLM が spec に基づいて提案する

フリーチャットが発散するのは、「spec なしでいきなりペアプロを始める」のと同じです。何を作るか合意しないままコードを書き始めたら、手戻りが発生するのは当然。インフラ選定も同じで、評価軸(= spec)を先に定義してから AI に渡すことで、出力が安定します。

実装: Pikinfra の設計

全体アーキテクチャ

Client (React)                    Server (Next.js API Route)
┌──────────────┐                 ┌──────────────────────────┐
│              │  POST           │                          │
│   Wizard     │──answers──────→ │  Zod Validation          │
│  (7 steps)   │                 │         ↓                │
│              │                 │  calculatePreScore()     │
│              │                 │   → tier: "startup"      │
│              │                 │         ↓                │
│              │                 │  Claude API              │
│   Result     │ ←───JSON──────  │   → Recommendation JSON  │
│              │                 │                          │
└──────────────┘                 └──────────────────────────┘

技術スタック:

Next.js 15 (App Router) + TypeScript
Tailwind CSS v4
Claude API (@anthropic-ai/sdk)
Zod (バリデーション)
framer-motion (トランジション)

Layer 1: 構造化入力 ― フェーズで質問を分岐させる

最大の設計判断は、最初にフェーズを聞くことでした。

src/lib/questions.ts
export const wizardSteps: WizardStep[] = [
  {
    id: "phase",
    title: "プロジェクトのフェーズ",
    description: "フェーズに応じて質問数と提案の粒度が変わります。",
    questions: [{
      id: "phase",
      type: "single",
      options: [
        {
          value: "poc",
          label: "PoC / プロトタイプ",
          hint: "→ 最短2ステップ。ホスティング中心の最小構成を提案します",
        },
        {
          value: "mvp",
          label: "MVP / 初期リリース",
          hint: "→ 基本的な構成(ホスティング + DB + 認証)を提案します",
        },
        {
          value: "production",
          label: "本番運用 / グロース",
          hint: "→ 全質問に回答いただき、フルスタック構成を提案します",
        },
        {
          value: "review",
          label: "既存構成の見直し",
          hint: "→ 全質問 + 移行パスやリスクを重点的に提案します",
        },
      ],
    }],
  },
  // Step 3: 規模感 ← MVP以上でのみ表示
  {
    id: "scale",
    title: "規模感",
    showWhen: ["mvp", "production", "review"],  // ← PoC では非表示
    // ...
  },
  // Step 4: セキュリティ ← 本番/見直しのみ
  {
    id: "security",
    showWhen: ["production", "review"],  // ← PoC/MVP では非表示
    // ...
  },
  // ...
];

ウィザード側では showWhen に基づいてステップをフィルタリング:

src/app/wizard/page.tsx
function getVisibleSteps(answers: WizardAnswers) {
  const phase = answers.phase?.[0] as ProjectPhase | undefined;
  return wizardSteps.filter((step) => {
    if (!step.showWhen) return true;
    if (!phase) return true;
    return step.showWhen.includes(phase);
  });
}
フェーズ ステップ数 スキップ
PoC 4 規模感 / セキュリティ / チーム
MVP 6 セキュリティ
本番 / 見直し 7 なし

PoC の人に「HIPAA 対応は必要ですか?」とは聞きません。

hint: 選択の影響を可視化する

各選択肢に hint フィールドを持たせて、その選択が最終的な提案にどう影響するかを表示します。

{
  value: "nextjs",
  label: "Next.js",
  hint: "→ Vercel が最適。Cloudflare / AWS Amplify も候補",
}
{
  value: "100000+",
  label: "100,000+",
  hint: "→ フルクラウド構成(AWS/GCP/Azure)を推奨",
}

対象ユーザーはある程度インフラがわかるエンジニアです。ブラックボックスでは信頼されません。「この選択肢を選ぶと、AI は何を重視して提案するのか」が事前に見えることで、ユーザーは安心して回答できます。

型定義:

src/lib/types.ts
export type QuestionOption = {
  value: string;
  label: string;
  description?: string;
  /** 選択した場合の提案への影響を示すテキスト */
  hint?: string;
};

export type WizardStep = {
  id: string;
  title: string;
  description: string;
  questions: Question[];
  /** このステップを表示するフェーズ(未指定なら常に表示) */
  showWhen?: ProjectPhase[];
};

Layer 2: ルールベースのプレスコアリング

回答を 5 つの軸で 0-10 にスコア化し、tier(hobby / startup / growth / enterprise)を決定します。

src/lib/scoring.ts
export function calculatePreScore(answers: WizardAnswers): PreScore {
  const phase = (first(answers, "phase") || "production") as ProjectPhase;

  const scalability = scoreScalability(answers);       // 0-10
  const security = scoreSecurity(answers);             // 0-10
  const budget = scoreBudget(answers);                 // 0-10
  const teamExperience = scoreTeamExperience(answers); // 0-10
  const operationalComplexity = scoreOperationalComplexity(answers);

  const baseTier = determineTier(scalability, security, budget, teamExperience);
  const tier = applyPhaseOverride(phase, baseTier);

  return { tier, categories: { scalability, security, budget, teamExperience, operationalComplexity } };
}

ポイントは applyPhaseOverride:

function applyPhaseOverride(
  phase: ProjectPhase,
  baseTier: InfraTier
): InfraTier {
  switch (phase) {
    case "poc":
      return "hobby"; // PoC は常に hobby
    case "mvp":
      return baseTier === "enterprise" || baseTier === "growth"
        ? "startup" // MVP は最大でも startup
        : baseTier;
    default:
      return baseTier;
  }
}

予算$2,000 と答えても、PoC なら hobby ティアになります。フェーズが最も強い制約条件として機能する。

Layer 3: Claude API へのプロンプト設計

フェーズごとに異なる指示をプロンプトに注入します。

src/lib/prompts.ts
const PHASE_INSTRUCTIONS: Record<ProjectPhase, string> = {
  poc: `## フェーズ: PoC / プロトタイプ
ユーザーはアイデアや技術の検証段階です。
- 最小構成のみ提案すること。ホスティング1つで済むならそれだけでOK。
- DB が不要なら省略。認証・CDN・監視も原則省略。
- 「とにかく早くデプロイできる」ことを最優先。`,

  mvp: `## フェーズ: MVP / 初期リリース
- 基本構成(ホスティング + DB + 認証)を中心に提案。
- CDN・監視は不要なら省略。`,

  production: `## フェーズ: 本番運用 / グロース
- フルスタック構成を提案。
- スケーラビリティ、セキュリティ、コストのバランスを考慮。`,

  review: `## フェーズ: 既存構成の見直し
- risks に移行リスクを必ず含める。
- scaling_path に段階的な移行ステップを記載。`,
};

API Route では、プレスコアの結果をユーザープロンプトに含めて Claude に渡します:

src/app/api/recommend/route.ts
export async function POST(request: Request) {
  const { answers } = parsed.data;
  const preScore = calculatePreScore(answers);
  const userPrompt = buildUserPrompt(answers, preScore);

  const message = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 4096,
    system: SYSTEM_PROMPT,
    messages: [{ role: "user", content: userPrompt }],
  });

  // JSON をパースして返却
  const jsonMatch = textBlock.text.match(/\{[\s\S]*\}/);
  const recommendation: Recommendation = JSON.parse(jsonMatch[0]);
  return NextResponse.json({ recommendation, preScore });
}

LLM に渡されるユーザープロンプトのイメージ:

## フェーズ: PoC / プロトタイプ
最小構成のみ提案すること。ホスティング1つで済むならそれだけでOK。

## ユーザー回答
【プロジェクトの種類】
  プロジェクトの種類: SaaS アプリケーション

【技術スタック】
  フロントエンド: Next.js
  バックエンド: なし(フロントのみ)
  データベース: 不要

【予算・優先度】
  月間インフラ予算: 無料〜$20
  優先度: 開発スピード, コスト

## プレスコアリング結果
- 推定ティア: hobby
- スケーラビリティ: 0/10
- セキュリティ: 0/10
- 予算レベル: 1/10
- チーム経験値: 1/10

この入力なら、Claude は「Vercel にデプロイ。以上。」に近い回答を返します。フリーチャットで起きていた「DB も必要かも」「CDN も入れた方が」という余計な提案が消える。

レスポンスの型

LLM の出力を JSON で構造化し、不要なフィールドは省略可能にしています:

src/lib/types.ts
export type RecommendedStack = {
  hosting: StackItem;          // 必須
  database?: StackItem;        // 省略可
  auth?: StackItem;            // 省略可
  cdn?: StackItem;             // 省略可
  monitoring?: StackItem;      // 省略可
  additional?: AdditionalStackItem[];
};

export type Recommendation = {
  summary: string;
  tier: InfraTier;
  stack: RecommendedStack;
  architecture_notes: string;
  estimated_monthly_cost: EstimatedCost;
  scaling_path: string;
  risks: string[];
};

hosting だけが必須で、残りはオプショナル。PoC で「Vercel 一択」という提案では、database 以下がすべて省略された JSON が返ります。

フリーチャットとの比較

フリーチャット 構造化 → LLM
入力 自然言語(曖昧) 選択式(網羅的)
評価軸 暗黙的 明示的(hint で可視化)
方向性の決定 LLM 任せ(ブレる) ルールベース(再現性 100%)
具体的な選定 LLM LLM(方向性が固定された状態で)
過剰提案 起きやすい フェーズで制御

このパターンの汎用性

この 構造化入力 → ルールベース前処理 → LLM のパターンは、インフラ選定以外にも使えます。

  • 技術選定: フレームワーク / ライブラリの選定ツール
  • 見積もり: プロジェクト規模から工数を概算
  • レビュー: コードレビューの観点を構造化してから LLM に渡す
  • 診断系ツール全般: 「質問に答えたら AI が判断する」系のプロダクト

共通するのは、LLM に判断させたいが、判断の枠組み(= spec)は人間が設計すべきという構造です。

LLM は「与えられた spec の中で最適な選択をする」のが得意です。spec なしで丸投げすると、あらゆる可能性を考慮しようとして発散する。これは LLM の欠陥ではなく、入力設計の問題です。

spec 駆動開発が「実装の前にテストを書く」ことで品質を担保するように、AI 活用においても「AI に渡す前に要件を構造化する」ことで出力の品質を担保できます。

まとめ

  • AI にフリーで「インフラどうしたらいい?」と聞くと発散する
  • 原因は 評価軸が事前に定まっていないこと
  • 解決策: 構造化入力 → プレスコアリング → LLM の 3 層設計
  • フェーズ(PoC / MVP / 本番)で質問数・提案粒度を動的に変える
  • 選択肢の hint で提案ロジックの透明性を確保する
  • 方向性はルールベース(再現性 100%)、具体的な選定は LLM に任せる

「AI に何を渡すか」を設計する。これが 2026 年以降の AI 活用で最も重要なスキルだと思います。

リンク

Discussion