「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: 構造化入力 ― フェーズで質問を分岐させる
最大の設計判断は、最初にフェーズを聞くことでした。
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 に基づいてステップをフィルタリング:
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 は何を重視して提案するのか」が事前に見えることで、ユーザーは安心して回答できます。
型定義:
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)を決定します。
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 へのプロンプト設計
フェーズごとに異なる指示をプロンプトに注入します。
const PHASE_INSTRUCTIONS: Record<ProjectPhase, string> = {
poc: `## フェーズ: PoC / プロトタイプ
ユーザーはアイデアや技術の検証段階です。
- 最小構成のみ提案すること。ホスティング1つで済むならそれだけでOK。
- DB が不要なら省略。認証・CDN・監視も原則省略。
- 「とにかく早くデプロイできる」ことを最優先。`,
mvp: `## フェーズ: MVP / 初期リリース
- 基本構成(ホスティング + DB + 認証)を中心に提案。
- CDN・監視は不要なら省略。`,
production: `## フェーズ: 本番運用 / グロース
- フルスタック構成を提案。
- スケーラビリティ、セキュリティ、コストのバランスを考慮。`,
review: `## フェーズ: 既存構成の見直し
- risks に移行リスクを必ず含める。
- scaling_path に段階的な移行ステップを記載。`,
};
API Route では、プレスコアの結果をユーザープロンプトに含めて Claude に渡します:
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 で構造化し、不要なフィールドは省略可能にしています:
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 活用で最も重要なスキルだと思います。
リンク
- Pikinfra: https://pikinfra.com
Discussion