【実機検証】Foundry LocalのPython SDKをWindowsネイティブで動かす ⋯4つの罠と日本語応答までの道のり

前回の記事ではFoundry Localの調査・考察編として、Microsoftの戦略やOllama/LM Studioとの違いについて書いてみました。

今回はハンズオン編として、Foundry Localを動かしてみようと思います。

前回も書いた点ですが、ポイントは以下の2つです。

  • Windowsネイティブで動かす(NPU/GPUの自動活用ができる、Foundry Localの真価が出る環境)
  • 既存のOpenAI SDKコードを"ほぼそのまま"動かせる(移植の手間が最小限)

⚠️今回はGPUもNPUもない環境で試しているので、CPU版のモデルで動かす事になっていますが、NPU/GPU搭載機なら同じコードで自動的にそちらが使われるというのも体験してみたいところです。

また、OllamaやAzure OpenAIで書いていたコードがどれだけ少ない変更で動かせるのか、実際のBefore/Afterを並べて比較してみます。

※本記事の情報は2026年4月時点のものです。Foundry LocalはまだGA直後で動きが早いので、最新情報は公式ドキュメントも併せて確認してみてください。



なぜWindowsネイティブで動かすのか

最初に環境選択の話だけしておきます。Foundry LocalはWindows / macOS / Linux(x64)で動きますし、WSL2でも動きます。ただ、自分がおすすめしたいのはWindowsネイティブです。

理由はシンプルで、NPUを使えるのはWindowsネイティブ環境だけだからです。

環境 NPU NVIDIA GPU Intel/AMD iGPU
Windowsネイティブ ✅ 使える ✅ 使える ✅ 使える
WSL2(Ubuntu等) ⚠️ 使えない ✅ CUDA透過 △ 限定的

最近のCopilot+PCやIntel Core Ultra系のマシンを使っている方なら、NPUを使わない手はないと思います。Foundry Localエイリアス指定だけでハードウェアを自動選択してくれるので、NPU搭載機ならQNN(Qualcomm)やOpenVINO(Intel)経由でNPUが自動的に使われます。

残念ながら、WSL2の仮想化層からはNPUにアクセスできないので、ここがWSL2との大きな差です(WSL2については記事の最後にも触れます)。 いつもだったらWSLを使用するのですけどね🥲


Foundry Local本体のインストール

Windowsネイティブのインストールはwingetで一発です。

wingetはMicrosoftが公式に提供しているWindowsのパッケージマネージャで、Windows 11なら標準で入っています(Windows 10は1809以降が対象)。LinuxのaptやmacOSのbrewに近い感覚で、CLIから一発でアプリを入れられるツールですね。最近はMicrosoft公式の配布物もwinget経由で揃うことが増えてきているようです😊

PS> winget install Microsoft.FoundryLocal

これでCLIとバックグラウンドサービスが同時に入ります。バージョン確認をしてみます。

PS> foundry.exe --version

⚠️ ただし、インストール直後はサービスがまだ起動していないので、service statusを叩くとこんな感じで出ます。

PS> foundry.exe service status
🔴 Model management service is not running!
To start the service, run the following command: foundry service start

メッセージにある通り、最初は手動でサービスを起動します。

PS> foundry service start

起動後にもう一度 foundry service status を叩けば、今度はグリーンで動作確認できるはずです。「インストール = サービス起動済み」と思い込んでいると一瞬詰まるポイントなので、ここは先に書いておきます😊

初回のfoundry model list実行時に、マシンのハードウェア構成に合わせた実行プロバイダ(CUDA、QNN、OpenVINOなど)が自動でダウンロードされます。(この実行は後述します)

ここがFoundry Localの便利な点で、「NPU使うならこのドライバ入れて、CUDA使うならこのバージョンで…」みたいな手作業が要らないんですよね🤩


Pythonの実行環境はWindowsネイティブのuvで作る

Pythonでサンプルコードを動かす前に、Python環境を整えておきます。今回はWindowsネイティブで動かすので、uvもWindows上にインストールします。WSL2側のuvしか使ったことがないという方も結構いると思うので、ここはひと通り順番に書いておきます。

uvはRust製の高速Pythonパッケージマネージャで、pip+venv+pyenvをまとめて置き換えられるツールです。

とにかく速いのと、プロジェクト単位で完全に独立した環境が作れるのが便利。Foundry LocalのSDKは複数バージョン(WinML版と汎用版)を環境ごとに分けたいので、uvと相性がいいです。

uvをWindowsにインストールする

PowerShellを管理者でなく普通のユーザー権限で開いて、以下のコマンドを実行します。

PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

winget派の方はこちらでも入ります。

PS> winget install --id=astral-sh.uv -e

⚠️インストール後はPowerShellを開き直してから確認します(PATHの反映に再起動が必要なため)。

PS> uv --version
uv 0.x.x

⚠️もし「uvは、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。」と出る場合は、PATHが通っていない可能性があります。インストール時に表示される配置先(通常 C:\Users\<ユーザー名>\.local\bin\)を環境変数PATHに追加するか、PowerShellを完全に閉じて開き直してみてください。

Pythonランタイムをuvで入れる

uvPython本体もuv自身が管理してくれるので、Python.orgやMicrosoft Storeから別途入れる必要はありません(逆に既に入れている場合と競合することもあるので注意)。

任意のバージョンを入れるならこんな感じです。

PS> uv python install 3.12

入っているPythonの一覧はこれで確認できます。

PS> uv python list

プロジェクト用の仮想環境を作る

作業用のフォルダを作って、その中に仮想環境を作ります。今回は事前にインストールしていたPython 3.13で進めます。

PS> mkdir foundry-local
PS> cd foundry-local
PS> uv venv

すると.venvフォルダができます。アクティベートはこんな感じで。

PS> .venv\Scripts\activate

⚠️ ここでも引っかかる方がいるポイントですが、PowerShellの実行ポリシーによってはactivateが拒否される場合があります。その場合は以下を一度だけ実行しておけばOKです(現在のユーザーに対してのみリモート署名スクリプトを許可)。

PS> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

うまくアクティベートできるとプロンプトの先頭に(foundry-local)が付きます。

(foundry-local) PS C:\Users\xxx\foundry-local>

ここまで来ればOKです。

Python SDKと関連パッケージのインストール

Pythonの仮想環境ができたので、Foundry LocalのPython SDKをインストールします。Windowsネイティブで使う場合はWinML版のSDKがおすすめです。今回はOpenAI互換APIも使うのでopenaiもまとめて入れておきます。

(foundry-local) PS> uv pip install foundry-local-sdk-winml openai

uv pip install普通のpip installの高速版くらいに思ってもらえれば大丈夫です。

WinML版はWindows MLランタイムと統合されていて、NVIDIA CUDA / Qualcomm QNN / Intel OpenVINOなどの実行プロバイダをOS経由で自動登録してくれるとのこと。汎用版foundry-local-sdkでも動きはしますが、ハードウェアアクセラレーション周りで差が出るようです。

⚠️ 注意点として、foundry-local-sdkfoundry-local-sdk-winml同じPython環境に同居させないほうがいいようです。ランタイム競合が起きます。仮想環境を分けるのが無難です(まあuvならプロジェクトごとに環境を切るのが普通なので、自然とそうなりますが😊)。

これで実行環境は整いました。


使えるモデルを確認してみる

まずはどんなモデルが使えるかを見てみます。CLIから確認します。

PS> foundry.exe model list

このコマンドは賢くて、自分のマシンで実行可能なモデルだけが列挙されますFoundry Localでは同じモデルに対してCPU向け・CUDA向け・QNN向けといった実行プロバイダ別のビルド(バリアントと呼びます)が用意されていて、その中から動かせるものだけが見えるイメージです。なのでNPU非搭載ならNPU向けバリアントは出てこないし、GPU非搭載ならCUDA版も出てきません。

ためしにqwen2.5-0.5bのバリアント一覧を見てみます。

PS> foundry.exe model list --filter alias=qwen2.5-0.5b

NPU搭載機なら、QNN版やOpenVINO版のバリアントが含まれて表示されるはずです。Pythonからも以下のように一覧取得できます。

list_models.py

# list_models.py
from foundry_local_sdk import Configuration, FoundryLocalManager

config = Configuration(app_name="foundry-hello")
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance

for m in manager.catalog.list_models():
    print(f"{m.alias:30s} ctx={m.context_length:>6}  tools={m.supports_tool_calling}")
    for v in m.variants:
        print(f"    - {v.id}")

実行はこんな感じ。

(foundry-local) PS> python list_models.py

⚠️ ハマりポイント: WinML Bootstrap警告について

実行すると、毎回先頭にこんな警告が出ます。

warn: FoundryLocalCore.Bootstrap[0]
      WinML Bootstrap.Initialize failed. WinML execution providers may not be available.
      System.Runtime.InteropServices.COMException (0x80070032): ...

HRESULT 0x80070032 は Win32 の ERROR_CALL_NOT_IMPLEMENTED で、WinML Bootstrapが必要な依存を見つけられないというサインのようです。Windows App Runtimeの依存かと思って 1.5 / 1.6 / 1.7 / 1.8 を全部入れてみましたが、警告は消えませんでした😅

ただ、

  • list_models() の結果はちゃんと取れる
  • モデル一覧・コンテキスト長・ツール対応有無も正しく見える
  • 各モデルのバリアント(generic-gpu / generic-cpu)も列挙される

という状態で、CPU推論には実害なしスマン。NPU/GPU搭載機での挙動は別途検証が必要そうですが、CPU環境ではこの警告を見つつ先に進んで大丈夫と判断しました。同じ警告で困っている方がいたら、コメントいただけると嬉しいです😊


既存のコードをFoundry Local化してみる

ここからが今回のメインです。

普段、OllamaやAzure OpenAIで書いていたPythonコードがどれだけ少ない変更でFoundry Localに移行できるのかを見ていきます。

Before: Ollamaを使っていたコード

たとえば、こんな感じのコードを書いていた方は多いと思います。OpenAI互換APIをOllamaのエンドポイントに向けて叩くパターンです。

ollama_sample.py

# ollama_sample.py
import openai

# Ollamaのローカルエンドポイントを叩く
client = openai.OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # ダミー
)

response = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": "必ず日本語で簡潔に回答してください。"},
        {"role": "user", "content": "黄金比について3行で説明してください。"}
    ]
)

print(response.choices[0].message.content)

⚠️事前準備として、ollamaのGUIを起動するか、別ターミナルでollama serveを起動してollama pull qwen2.5しておく必要があります。

After: Foundry Local版

これをFoundry Localに置き換えるとこうなります。

foundry_sample.py

# foundry_sample.py
import openai
from foundry_local_sdk import Configuration, FoundryLocalManager

# Foundry Localを起動 + Webサーバーモードを有効化
config = Configuration(
    app_name="foundry-openai-compat",
    web=Configuration.WebService(urls="http://127.0.0.1:8765"),
)
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance
manager.start_web_service()  # ★ Webサーバーを明示的に起動

# モデルの取得・ダウンロード・ロード
model = manager.catalog.get_model("qwen2.5-1.5b")
model.download()
model.load()

# ★ ここから下は、Ollama版とほぼ同じ ★
client = openai.OpenAI(
    base_url="http://127.0.0.1:8765/v1",
    api_key="not-needed"
)

response = client.chat.completions.create(
    model=model.id,
    messages=[
        {"role": "system", "content": "必ず日本語で簡潔に回答してください。"},
        {"role": "user", "content": "黄金比について3行で説明してください。"}
    ]
)

print(response.choices[0].message.content)

# 後片付け(OGA leak警告の抑制)
model.unload()
manager.stop_web_service()

実行するときも、uvでアクティベートした環境からそのまま動かせます。

(foundry-local) PS> python foundry_sample.py

⚠️ ハマりポイント記録

このAfter版のコード、「OllamaからbaseURLを差し替えるだけ」とはいかず、実は実機で動くまでに何ヶ所か詰まりました😅 同じところで止まる方がいると思うので残しておきます。

web= に dict を渡すと AttributeError

公式リファレンスっぽい雰囲気で web={"urls": "..."} と書いてみたら、内部で self.web.external_url のような属性アクセスが走って怒られました。Configuration.WebService(urls=...) というネスト型のクラスインスタンスを渡すのが正解です。

WebService(urls=...) だけではサーバーが起動しない

設定を渡しただけでは Webサーバーは立ち上がらず、manager.start_web_service() を明示的に呼ぶ必要があります。

また、終了時は model.unload()manager.stop_web_service() をセットで呼ぶのを忘れないようにしましょう(両方やらないと OGA Error: ... were leaked. のクリーンアップ警告が残ります)。

③ モデル名はカタログの実エイリアスを使う

get_model("qwen2.5") のような曖昧な名前だと None が返ってきて、続く .download()AttributeError になります。foundry model list で確認できる実エイリアス(例: qwen2.5-1.5b)を指定するのが正解です。

④ qwen系は黙っていると中国語で返してくる

これはモデル特性ですが、最初に動かした時は応答がガッツリ中国語でした😅 messages の先頭に system プロンプトを1行入れるだけで日本語に固定できます。

messages=[
    {"role": "system", "content": "必ず日本語で簡潔に回答してください。"},
    {"role": "user", "content": "黄金比について3行で説明してください。"}
]

ここまで4つ踏んでようやくちゃんと日本語で「黄金比は…」と返ってきた時はようやくといったところです😊

何が変わったか

差分を整理するとこうなります。

項目 Ollama版 Foundry Local版
事前準備 ollama serve & ollama pull を別途実行 コード内で完結(start_web_service()で明示起動)
エンドポイント http://localhost:11434/v1 http://127.0.0.1:8765/v1
モデル指定 "qwen2.5"(文字列) model.id(エイリアス取得後)
OpenAI SDKの呼び出し部分 同じ 同じ

ポイントは、openai.OpenAI(...)以降のコードは何も変わっていないという点です。chat.completions.create()の使い方も同じ、レスポンスの形も同じ。OpenAI互換APIである恩恵がここで効いてきます

つまり、すでにOllamaで動いているコードがある方なら、

  1. 冒頭にFoundry Localの起動&モデルロードのコードを加える
  2. base_urlを差し替える

この2ステップだけで移植できることになります。LangChainやLlamaIndexで書いていた既存資産も、同じ要領でほぼそのまま動くはずです😊


過去のコードの再利用

実際、自分が以前ブログで書いたExcelからPower Query経由でローカルLLMを叩くような記事のサンプルコードも、HTTPエンドポイント周りだけ書き換えれば動くはずです。(今後検証してみます)

OpenAI互換のサーバーモードで動かしている限り、

  • LangChain
  • LlamaIndex
  • Open WebUI

このあたりは全部そのまま使えるわけです。これは移行のしやすさという意味では地味に大きな利点ですね。


ストリーミング出力もOpenAI SDKと同じ

ChatGPTっぽくトークンを流しながら表示する例も置いておきます。

foundry_stream.py

# foundry_stream.py
import openai
from foundry_local_sdk import Configuration, FoundryLocalManager

# Foundry Localを起動 + Webサーバーモードを有効化
config = Configuration(
    app_name="foundry-openai-compat",
    web=Configuration.WebService(urls="http://127.0.0.1:8765"),
)
FoundryLocalManager.initialize(config)
manager = FoundryLocalManager.instance
manager.start_web_service()

# モデルの取得・ダウンロード・ロード
model = manager.catalog.get_model("qwen2.5-1.5b")
model.download()
model.load()

client = openai.OpenAI(
    base_url="http://127.0.0.1:8765/v1",
    api_key="not-needed"
)

# ★ ストリーミングの場合は stream=True を渡すだけ ★
stream = client.chat.completions.create(
    model=model.id,
    messages=[
        {"role": "system", "content": "必ず日本語で簡潔に回答してください。"},
        {"role": "user", "content": "再帰関数とは何ですか?"}
    ],
    stream=True
)

for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
print()

# 後片付け
model.unload()
manager.stop_web_service()

この部分もOpenAI SDKと作法が完全に同じなので、特に学び直しは不要です。


(未検証)ハードウェアの自動選択を実感する

この部分はまだ試せていないのですが、Foundry Localの面白いところで、同じコードが環境によって違うハードウェアで動くという点があります。

たとえば、モデルの取得部分でエイリアス指定をすると、

model = manager.catalog.get_model("qwen2.5-0.5b")

このエイリアス指定だけで、

  • Copilot+PC(Snapdragon X / NPU) → QNN NPU版
  • Intel NPU搭載機(Core Ultra系) → OpenVINO版
  • NVIDIA GPU搭載機 → CUDA版
  • それ以外 → CPU版(INT4/INT8量子化)

上記が自動で選ばれますようです。アプリ開発者はハードウェア検出のコードを書かなくていいというわけです。

⚠️ もし「絶対このバリアントで動かしたい」というケースは、エイリアスではなくモデルIDを直接指定すれば固定できます。

model = manager.catalog.get_model("qwen2.5-0.5b-instruct-generic-cpu")

モデルはどこに保存される?

Windowsの場合、デフォルトは以下になるようです。

C:\Users\<ユーザー名>\.foundry\cache\models\
C:\Users\<ユーザー名>\.foundry-openai-compat\cache\models\

後者(...openai-compat...)はOpenAI互換のサーバモードで使うモデルが入るようです。

モデル1つで数GB〜十数GBになるので、Cドライブの容量には注意です😨

別ドライブに退避させたい場合は以下で。

PS> foundry service set --cachedir D:\foundry-cache

確認はfoundry cache locationコマンドや、foundry cache listでダウンロード済みモデル一覧を見られます。

⚠️ 既知のトラブルですが、Windowsのユーザー名にスペースが含まれるとキャッシュ一覧が空になる不具合がGitHub Issueで報告されています。該当する方は最初に--cachedirで別パスに退避するのが無難そうです。


WSL2でも動くには動くけれど…

ここまでWindowsネイティブの話をしてきましたが、最後にWSL2の話を少しだけ。

普段の開発環境がWSL2中心、という方は多いと思います(自分もそうです)。WSL2でもインストールは簡単で、uvがすでに入っていれば以下で済みます。

$ uv venv --python 3.12
$ source .venv/bin/activate
(.venv) $ uv pip install foundry-local-sdk openai

WSL2側ではWinML版ではなく汎用版のfoundry-local-sdkを使います。NVIDIA GPU搭載機なら、Windows側にドライバが入っていればWSL2から透過的にCUDAが使えます。Pythonコード自体はWindowsネイティブ版とまったく同じです。

ただし冒頭にも書いた通り、WSL2からはNPUにアクセスできません


動かしてみての感想

実際に動かしてみての率直な感想です。

よかった点🤗

  • 既存のOpenAI SDKコードがほぼそのまま動く(これは想像以上に楽でした)
  • モデルのエイリアス指定で自動的に最適なハードウェアが使われる
  • ONNX Runtimeベースなので推論速度は素直に速い
  • NPU活用までドライバ周りの面倒を見てくれる(Windowsネイティブ版)

気になった点🤔

  • モデルカタログが狭い(日本語特化モデルが現時点では無い)
  • GUIは無い(別途Open WebUIなどを使う必要がある)
  • 情報が英語中心、日本語の事例がまだ少ない
  • 新モデルの追加ペースはOllamaよりだいぶゆっくり

日本語モデルについて🙄

日本語特化モデルが今後カタログに追加されるかどうかは要注目ですね。メジャーなモデルがONNX形式で揃ってくるとかなり熱いのですが😊


おわりに

実際に動かしてみて思ったのは、OpenAI SDKを使った既存資産との互換性が想像以上に高いということでした。

  • インストールはwinget+uv+uv pipで完結
  • 既存コードはbase_urlの差し替えと初期化処理の追加だけで動く
  • Windowsネイティブで動かせばNPU/GPU/CPUが自動選択

NPUのある環境で、ローカルLLMを楽に動かしたい」という方には、現時点でFoundry Localは有力な選択肢になるかなと思います。特に手元のコード資産を活かしたまま試せる点が、移行のハードルを下げてくれていますね。

今後モデルがさらに拡充されたら、再度検証してみたいと思います🤗


参考リンク