はじめに
!!!!!! モデルおよびコードの利用は自己責任でお願いします !!!!!
本稿は「ローカル LLM で AI コーディング支援環境を構築する」シリーズの第⑥回です、 今回は第②回で取り上げました Foundry Local の続編をお届けいたします。
当時うまくいかなかった VSCode への組み込みもうまくいきました、が開発端末のスペックの問題で動作させるまでに越えたひと山もふた山についても記載しています。
パッと使っていただけるようなスクリプトもご用意いたしましたので、参考にしていただけますと幸いです。
前提条件
第②回とほぼ同じですが再掲いたします、開発端末のスペックが今回も大いに足を引っ張っています。(その分勉強にもなりましたが)
- クラウド生成 AI サービスは利用不可
- 開発端末はローカル PC(GPU: NVIDIA T500、VRAM: 4GB)
- エディタは VSCode と GitHub Copilot CLI
- ローカル LLM サーバーは Foundry Local
- モデルは Qwen2.5-coder:1.5b
セットアップ手順
今回 Foundry Local のセットアップをスクリプト化しました。
- Foundry Local のインストール
- Foundry Local の起動
- モデルのダウンロード
- ヘルスチェック
- 推論テスト
と言う流れで処理を行っています。
yaml からパラメータを読み出す実装を入れていますが、お試しの際には当該パラメータを直接記載して使ってください。
Write-Host "=== Install Foundry Local ==="
# config.yaml を section.key 形式の辞書に変換する
function Read-YamlConfig {
param([string]$Path)
$result = @{}
$section = ""
foreach ($line in Get-Content $Path) {
if ($line -match '^(\w[\w_]*):\s*$') { $section = $Matches[1] }
elseif ($line -match '^\s+(\w[\w_]*):\s*(.+)$') { $result["$section.$($Matches[1])"] = $Matches[2].Trim() }
}
return $result
}
function Wait-ForServiceReady {
param(
[string]$BaseUrl,
[int]$TimeoutSeconds = 60,
[int]$PollIntervalSeconds = 5
)
# OpenAI 互換の応答を複数エンドポイントで順に確認する
$healthEndpoints = @(
"$BaseUrl/openai/v1/models"
"$BaseUrl/v1/models"
"$BaseUrl/health"
$BaseUrl
)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while ((Get-Date) -lt $deadline) {
foreach ($ep in $healthEndpoints) {
try {
Invoke-WebRequest -Uri $ep -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop | Out-Null
return $true
} catch {
}
}
Start-Sleep -Seconds $PollIntervalSeconds
}
return $false
}
$configPath = Join-Path $PSScriptRoot "..\foundry\config.yaml"
$config = Read-YamlConfig -Path $configPath
$model = $config['model.default']
$baseUrl = $config['foundry.base_url']
$port = $config['foundry.service_port']
Write-Host "モデル: $model"
if (-not (Get-Command foundry -ErrorAction SilentlyContinue)) {
Write-Host ""
Write-Host "=== Install Foundry Local ==="
winget install Microsoft.FoundryLocal -e --silent
# インストール後に PATH を再読み込み
$env:PATH = [System.Environment]::GetEnvironmentVariable('PATH','Machine') + ';' +
[System.Environment]::GetEnvironmentVariable('PATH','User')
}
Write-Host ""
Write-Host "=== Start Foundry Local ==="
# サービス設定は config.yaml の値で上書きする
Write-Host "サービス設定を反映中..."
foundry service set --port $port --show *> $null
if ($LASTEXITCODE -ne 0) {
Write-Warning "サービス設定の反映に失敗しました (exit=$LASTEXITCODE)"
Write-Host "=== Setup Failed ==="
exit 1
}
# 既に起動済みならそのまま使い、未起動なら起動して待つ
if (Wait-ForServiceReady -BaseUrl $baseUrl -TimeoutSeconds 1) {
Write-Host "Foundry Local はすでに応答しています。"
Write-Host " → 起動済みのサービスをそのまま利用します"
} else {
Write-Host "サービス起動を待機中..."
Start-Process foundry -ArgumentList @('service', 'start') -NoNewWindow | Out-Null
if (-not (Wait-ForServiceReady -BaseUrl $baseUrl -TimeoutSeconds 30)) {
Write-Warning "サービスの起動確認ができませんでした。"
exit 1
}
}
Write-Host ""
Write-Host "=== Download Model ==="
# モデルをローカルに取得して読み込み、以降の推論に備える
foundry model download $model *> $null
if ($LASTEXITCODE -ne 0) {
Write-Warning "モデルのダウンロードに失敗しました (exit=$LASTEXITCODE): $model"
Write-Host "=== Setup Failed ==="
exit 1
}
foundry model load $model *> $null
if ($LASTEXITCODE -ne 0) {
Write-Warning "モデルのロードに失敗しました (exit=$LASTEXITCODE): $model"
Write-Host "=== Setup Failed ==="
exit 1
}
Write-Host "モデルのダウンロードが完了しました"
Write-Host ""
Write-Host "=== 簡易テスト 1/2: ヘルスチェック ==="
# サービスが外部から応答できる状態かを確認する
if (-not (Wait-ForServiceReady -BaseUrl $baseUrl -TimeoutSeconds 30)) {
Write-Warning "[NG] サービスに接続できません: $baseUrl"
Write-Host "=== Setup Failed ==="
exit 1
}
Write-Host "[OK] ヘルスチェック成功: $baseUrl"
Write-Host ""
Write-Host "=== 簡易テスト 2/2: 推論テスト ==="
# 実際にモデルへ問い合わせて、応答内容を確認する
$inferenceOutput = & foundry model run $model --prompt "Reply with only the word PASS" --retain 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
Write-Warning "[NG] 推論テスト失敗: foundry model run が失敗しました"
Write-Host $inferenceOutput
Write-Host "=== Setup Failed ==="
exit 1
}
if ($inferenceOutput -match '(?m)\bPASS\b') {
Write-Host "[OK] 推論テスト成功: レスポンス = 'PASS'"
} else {
Write-Warning "[NG] 推論テスト失敗: 期待した応答を確認できませんでした"
Write-Host $inferenceOutput
Write-Host "=== Setup Failed ==="
exit 1
}
Write-Host ""
Write-Host "=== Setup Complete ==="
チャットにモデルを追加
次に VSCode のチャットにモデルを追加します。
- Ctrl + Shift +P
- Chat: Configure Language Models
- モデルの追加
- 任意のグループ名
- 任意の API Key(yaml の記載で消してます)
と入力するとエディタが開きますので、
[
{
"vendor": "customendpoint",
"models": [
{
"id": "qwen2.5-coder-1.5b-instruct-cuda-gpu:4",
"vision": false,
"maxOutputTokens": 16000,
"url": "http://localhost:5272/v1/chat/completions",
"toolCalling": true,
"name": "qwen2.5-coder-1.5b (Local)",
"maxInputTokens": 32000
}
],
"name": "Foundry Local",
"apiType": "chat-completions"
}
]
と言う感じで設定してください。
設定後チャットのモデルをクリックすると、
うまいこと認識しました!
では会話をしてみましょう、
残念ながら作業中の状態が続きレスポンスは返ってきませんでした。GPU 使用が高止まりしたまま応答がなく最終的にこちらから処理をキャンセルしています。
これはモデル自体ではなく呼び出し側(クライアント)の挙動に原因がありそうだ、という見当をつけ原因調査に移行しました。
コンテキストの調査
おそらくコンテキスト周りだろうという想定が付いているので、コマンドでコンテキスト情報を取得できる GitHub Copilot CLI を使用して原因調査を行います。
まずは普通に GitHub Copilot CLI を起動してコンテキストを確認。
Context Usage
○ ○ ○ ○ ○ ○ ○ ○ ○ ○ qwen2.5-coder-1.5b-instruct-cuda-gpu:4 · 13k/33k tokens (39%)
○ ○ ○ ◌ ◌ ◌ ◌ ◌ ◌ ◌ ○ System Prompt 5.9k (18%)
◌ ◌ ◌ ◌ ◌ ◌ ◌ ● · · ◌ System Tools 6.8k (20%)
· · · · · · · · · · ● MCP Tools 155 (<1%)
· · · · · · · · · · ◉ Messages 0 (0%)
· · · · · · · · · · · Free Space 18.3k (55%)
· · · · · · ◎ ◎ ◎ ◎ ◎ Buffer 1.9k (6%)
会話してみます。
> こんにちは、自己紹介をしてください。
Operation cancelled by user
結果はチャットと同じくで最終的にこちらから処理をキャンセルしています。
デフォルト起動ではシステムプロンプト・ツール・MCP でコンテキストの約 39%(13k/33k)を消費していましたので、次に下記の 2 つを指定して CLI を起動しました。
--disable-builtin-mcps(組み込み MCP を無効化)
--available-tools=view(利用ツールを限定)
Context Usage
○ ○ ○ ○ ○ ◌ · · · · qwen2.5-coder-1.5b-instruct-cuda-gpu:4 · 3k/33k tokens (8%)
· · · · · · · · · · ○ System Prompt 2.2k (7%)
· · · · · · · · · · ◌ System Tools 381 (1%)
· · · · · · · · · · ● MCP Tools 0 (0%)
· · · · · · · · · · ◉ Messages 0 (0%)
· · · · · · · · · · · Free Space 28.6k (86%)
· · · · · · ◎ ◎ ◎ ◎ ◎ Buffer 1.9k (6%)
消費は 8%(3k/33k)まで削減、これなら何とか動くか。
> こんにちは、自己紹介をしてください。
こんにちは!私はGitHub Copilot CLIを用いて、さまざまなソフトウェアエンジニアリングタスクをサポートします。あなたのプロジェクトや課題について詳しく教えていただけますか?
少し時間はかかるものの応答が返ってくるようになりました!
この調査で開発端末程度のスペックではシステムコンテキストだけでも動作に影響が出てしまうことがわかりました。一般的にコンテキストエンジニアリングでは回答精度の担保と言う観点で取り扱われることが多いと思いますが、ローカル LLM の世界ではリソース観点でもシビアに捉える必要がありそうです。
逆に言えば、超軽量なコンテキストを設計できれば、低スペック環境でも用途特化の可能性を広げられます。
- 特定言語専用
- ユニットテスト専用
- コーディング規約レビュー専用
といった手元の開発作業に特化したコンテキストをローカル LLM と組み合わせる、という方向性は比較的容易に考えられそうです。
おわりに
今回は、Foundry Local のセットアップから紆余曲折ありながらもコンテキストを調整することで動作確認まで進めることができました。
ローカル LLM というと、
- モデルサイズ
- VRAM 使用量
- 推論速度
と言った観点に目が行きがちですが、今回のようにコンテキストそのものが実行コストになると言う点は、実際に触ってみないと気付きにくい部分だと思います。
このことからローカル LLM を実用的に運用するためには、
- コンテキストを必要最小限に絞る
- 用途ごとに専用の軽量プロンプトを設計する
- ツールや機能を適切に制限する
といったコンテキスト設計そのものが重要な要素 になることもわかりました。
精度を上げるためのコンテキストではなく動かすためのコンテキストを意識する必要がある、と言う点は印象的でしたね。
次回も Foundry Local でひとネタ書こうかと思います、Hugging Face モデルをコンパイルして使うことができるようなのでそのあたりで考えてます。
本稿を読んでいただきありがとうございました!

