Qwen3.5-122B-A10B ローカル推論による Django 5 旅行予約サイト生成テスト
クラウドAPIを使わず、Qwen3.5-122B-A10B (Q5_K_M) のローカル推論だけでDjango 5フルスタックWebアプリをMCPエージェント経由で生成させた検証記録
概要
クラウド API(Claude、GPT-4 等)を一切使わず、ローカルで動作する Qwen3.5-122B-A10B(Q5_K_M 量子化)だけで Django 5 の旅行予約サイトをフルスタック生成できるか検証した。MCP(Model Context Protocol)経由のコーディングエージェントにファイルの読み書きを任せ、初期仕様のワンショット生成(+手修正)の後、2回の機能拡張指示で Shop・Reviews・Search・Accounts を追加させた。各段階でいくつかの手修正が必要だったが、大枠のコード生成はすべてローカル推論で完結した。
クラウドの大規模モデルであれば、この程度のコード生成はすでに珍しくない。本稿の焦点は「手元のハードウェアで動く量子化 MoE モデルでも同等のことができるか」にある。
ローカル推論環境
| 項目 | 内容 |
|---|---|
| モデル | Qwen3.5-122B-A10B(MoE、アクティブ 10B / 総パラメータ 122B) |
| 量子化 | Q5_K_M(GGUF、3分割シャード) |
| 推論エンジン | ik_llama.cpp(OpenAI 互換 API サーバー) |
| MCP ツール(自作) | ctree(コードシンボル解析)、pathfinder(パス解決) |
| MCP ツール(OSS) | serena(セマンティックコード操作)、filesystem(ファイル読み書き)、ripgrep(検索) |
| コンテキスト使用量 | 約 77K プロンプトトークン |
ポイントは、推論がすべてローカルマシン上で完結していること。外部 API への通信は発生せず、トークン課金もない。エージェントは serena のシンボル操作ツールや filesystem の読み書きツール、ripgrep による検索、ctree によるコード構造解析、pathfinder によるパス解決といった複数の MCP サーバーを組み合わせて、Django アプリケーションを段階的に構築した。
検証の意図
クラウドではなくローカルで試す理由
Claude Sonnet や GPT-4o を使えばフルスタック Web アプリの生成は実績のある作業になっている。しかし、クラウド API に依存する限り:
- トークン単価の積み重ねでコストが膨らむ
- プロプライエタリコードを外部に送信する必要がある
- API の可用性やレート制限に縛られる
ローカル推論でクラウド同等の結果が得られるなら、これらの制約から解放される。122B パラメータの MoE モデルを Q5_K_M に量子化して ik_llama.cpp で動かした場合、実用的なコーディングエージェントとして機能するのか — それが本検証の問いだった。
ワンショット生成のアプローチ
完全な仕様書を一度に提供し、全ファイルを一括生成させる方式を採用した。これにより:
- ドメインモデルとビューの整合性が維持されやすい
- テンプレートとフォームの連携が仕様通りに実装されやすい
- シードデータがモデル定義と一致する
- 管理画面の設定がモデルフィールドと矛盾しにくい
仕様設計
技術スタック
| 要素 | 選定 | 理由 |
|---|---|---|
| バックエンド | Django 5.x | Python 3.13 互換、ORM 完備、管理画面自動生成 |
| フロントエンド JS | Alpine.js v3 (CDN) | Django テンプレート内で動作、ビルド不要 |
| CSS | Tailwind CSS | Material Design 3 インスパイアのユーティリティファースト |
| パッケージ管理 | uv | 高速な Python パッケージマネージャー |
| データベース | SQLite | 開発環境のみ、設定不要 |
初期ドメインモデル
初期仕様では3つのエンティティで旅行予約の基本フローを表現した:
TravelPackage 1 ──── N Tour
Tour 1 ──── N Reservation
TravelPackage(旅行商品):
- タイトル、スラグ、リージョン、期間、画像 URL
is_publishedフラグで公開制御min_priceプロパティで最安ツアー価格を動的取得
Tour(出発日程):
- 特定の出発日・帰着日・価格・残席数
- ステータス管理(available / soldout / cancelled)
is_reservableプロパティで予約可否判定
Reservation(予約):
- 顧客情報(氏名、メール、電話、人数、備考)
- Tour への FK で紐付け
- 決済なし(DB 保存のみ)
ビジネスルール
仕様書では以下のルールを明示的に定義した:
is_published=Trueのパッケージのみフロントエンドに表示status="available"のツアーのみ予約可能- 価格表示はパッケージ内の最安ツアー価格(
Min集約) - 予約フローはフォーム → 確認プレビュー → 完了の3ステップ
- 確認画面での POST で初めて DB に保存(セッション経由)
UI ワイヤーフレーム
仕様書ではレイアウトを ASCII アートで定義し、LLM が視覚構造を理解した上で Tailwind クラスを適用できるようにした:
┌─────────────────────────────────────────────────┐
│ HERO SECTION: bg-gradient-to-r from-blue-600 │
│ [SVG airplane animation flying across] │
│ "Discover Your Next Adventure" │
│ [ Browse All Packages → ] │
├─────────────────────────────────────────────────┤
│ FEATURED PACKAGES (card grid) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Card 1│ │Card 2│ │Card 3│ │
│ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────────────┘
Material Design 3 デザイン指定
| 役割 | Tailwind クラス | 用途 |
|---|---|---|
| Primary | bg-blue-600 | ボタン、リンク、アクティブ状態 |
| Surface | bg-white | カード、モーダル、フォーム背景 |
| Elevation Level 2 | shadow-md | メインカード、ヘッダー |
| Border Radius | rounded-2xl | カード、rounded-xl ボタン |
ホバーエフェクト:hover:shadow-lg hover:-translate-y-1 transition-all duration-200
ローカル LLM が生成したコード
Alpine.js フィルタリング
エージェントは仕様書の Alpine.js 指定を読み取り、クライアントサイドフィルタリングを生成した:
x-data="{
region: '',
maxPrice: ''
}"
x-show="(region === '' || $el.dataset.region === region) &&
(maxPrice === '' || parseInt($el.dataset.minPrice) <= parseInt(maxPrice))"
サーバーサイド API なしで動作するこの方式は、LLM が Alpine.js のリアクティブパターンを正しく理解していることを示している。
セッションベース予約フロー
3ステップの予約フローも仕様通りに生成された:
- フォーム入力(
/reserve/<tour_id>/):バリデーション後、データをセッションに保存 - 確認プレビュー(
/reserve/<tour_id>/confirm/):セッションから読み出し、サマリー表示 - 完了(
/reserve/success/):確認 POST で DB 保存、セッションクリア
# ReservationCreateView: POST でセッションに保存
request.session['reservation_data'] = form.cleaned_data
return redirect('reservation_confirm', tour_id=tour.id)
# ReservationConfirmView: POST で DB 保存
data = request.session.pop('reservation_data')
Reservation.objects.create(tour=tour, **data)
return redirect('reservation_success')
Django 管理画面
インライン編集を含む管理画面設定も、モデル定義と矛盾なく生成された:
class TourInline(admin.TabularInline):
model = Tour
extra = 1
@admin.register(TravelPackage)
class TravelPackageAdmin(admin.ModelAdmin):
list_display = ["title", "region", "duration_days", "is_published"]
list_editable = ["is_published"]
prepopulated_fields = {"slug": ("title",)}
inlines = [TourInline]
シードデータ
seed_demo マネジメントコマンドで5つのパッケージ(計14ツアー)を自動生成。リージョン別のフィルタリングテストに使用可能。
エージェントによる段階的拡張
初期のワンショット生成後に手修正を加えてベースを安定させた上で、2回の追加仕様指示を行った。エージェントは既存コードを読み取りながら以下のモデルとアプリを追加した(各段階でいくつかの手修正が発生):
| 追加アプリ | 主なモデル | エージェントの作業内容 |
|---|---|---|
shop | Shop(店舗情報) | models.py に Shop モデル追加、admin 登録、テンプレート作成 |
reviews | Review, Rating, ReviewPhoto | ツアーレビュー機能一式、承認フロー付き |
search | SearchIndex, TrendingKeyword | 検索インデックスとトレンド管理 |
accounts | User, UserProfile, UserActivity | ユーザー認証とプロフィール管理 |
エージェントは既存の tours/models.py を読み込んで FK 関係を確認し、新モデルとの整合性を取りながらコードを生成した。ただし、テンプレートの引用符エスケープや一部のインポート漏れなど、手修正が必要な箇所は各段階で発生した。完全な自動生成というよりは「8割をエージェントが書き、残り2割を人間が直す」という作業配分になった。それでも、この「既存コードの文脈を理解して拡張する」動作がローカル MoE モデルで機能していた点は注目に値する。
生成結果
画面キャプチャ




初期生成ファイル構成
| ファイル | 内容 |
|---|---|
tours/models.py | 3モデル + プロパティ + バリデーション |
tours/admin.py | 3モデルの管理画面設定 + インライン |
tours/forms.py | ReservationForm (ModelForm) |
tours/views.py | 6ビュー(Home, List, Detail, Form, Confirm, Success) |
tours/urls.py | URL パターン定義 |
tours/management/commands/seed_demo.py | シードデータ |
tours/templatetags/tour_filters.py | カスタムフィルター(multiply) |
tours/templates/tours/*.html | 7テンプレート |
static/css/custom.css | SVG アニメーション |
config/settings.py | INSTALLED_APPS 追加 |
動作確認
ローカル LLM が生成したコードは、手修正を加えた上で以下の動作を確認できた:
- マイグレーション実行後、サーバー起動可能
- シードデータの投入でフロントエンドにパッケージが表示
- フィルタリング・ソートの Alpine.js 機能が正常動作
- 予約フロー(入力→確認→完了)が完走
- 管理画面でのインライン編集が機能
手修正が必要だった主な箇所:
- パッケージ詳細ページのテンプレートで Django テンプレートタグの引用符エスケープ(
{{ tour.end_date|date:"M j, Y" }}が生文字列として表示) - 一部のインポート漏れや型の不整合
- 機能拡張時のテンプレート間の整合性
SVG アニメーション
ヒーローセクションには飛行機の飛行アニメーション(8秒ループ)と浮遊する雲(15秒ループ)を SVG + CSS キーフレームで生成。
得られた知見
1. ローカル MoE モデルのコーディング能力
Qwen3.5-122B-A10B (Q5_K_M) は、Django のモデル定義・ビュー・テンプレート・管理画面を整合性のある形で生成できた。77K トークンのコンテキストを使った段階的な拡張でも、既存コードとの整合性を維持していた。クラウドモデルと同等とまでは言えないが、仕様が明確であれば十分に実用的な生成結果を得られた。
2. 仕様書の詳細度が成否を分ける
ローカル LLM で精度を上げるには、仕様書側の情報量が鍵になる。特に以下が重要だった:
- ドメインモデルのフィールド定義(型、制約、デフォルト値)
- URL パターンとビューの対応表
- UI ワイヤーフレーム(ASCII アート形式でも十分)
- Tailwind クラスの具体的な指定
クラウドの大規模モデルは曖昧な指示からでも「いい感じ」に生成してくれることが多いが、ローカルモデルでは仕様の曖昧さがそのまま出力のブレに直結する。
3. 複数 MCP サーバーの連携
自作の ctree(コードシンボル解析)・pathfinder(パス解決)と、OSS の serena(セマンティックコード操作)・filesystem(ファイル読み書き)・ripgrep(検索)を組み合わせた MCP 環境は、ローカル LLM でも安定して動作した。エージェントは serena でシンボルを検索し、filesystem でファイルを読み書きし、ctree でコード構造を把握するという一連の流れを自律的に実行できた。
4. テンプレートのエスケープ問題
Django テンプレートタグの引用符処理は、ローカル LLM が苦手とする領域だった。{{ value|date:"M j, Y" }} のようなフィルター引数内の引用符が、生成時に正しくエスケープされないケースが見られた。この種の問題はクラウドモデルでも発生しうるが、ローカルモデルではやや頻度が高い印象。
まとめ
- ローカル推論だけで動作する Web アプリを生成:Qwen3.5-122B-A10B (Q5_K_M) でクラウド API なしにフルスタック Django サイトを構築
- 段階的な機能拡張にも対応:初期3モデルから6アプリへの拡張を、既存コードの文脈を理解しながら自律的に実行
- MCP エージェントとの統合:ローカル ik_llama.cpp + 複数 MCP サーバー(ctree / pathfinder / serena / filesystem / ripgrep)の組み合わせが実用的に機能
- 仕様書の質がローカル LLM の精度を左右:クラウドモデル以上に、明確な仕様定義が重要
- 各段階で手修正は発生:完全な自動生成ではないが、生成8割・手修正2割の作業配分でフルスタックアプリが構築できた

