Foundry Local を使用して gpt-oss へのアクセスを .NET(C#) コンソール アプリで実装する方法

2025/09/15
★★★

Foundry Local

2025 年 5 月に Foundry Local のパブリック プレビューが開始されました。Foundry Local は、エッジ デバイス向けの推論プラットフォームであり、Windows, macOS 向けに提供されており、容易にローカル デバイスで言語モデルを実行することができます。

Foundry Local ではクラウド ベースのモデル カタログが利用でき、提供モデル一覧からモデルをダウンロードできます。ダウンロードしたモデルは、CLI、REST API でアクセスできます。

今回は、Windows 11 において Foundry Local をセットアップし、2025 年 8 月 5 日に OpenAI からリリースされた gpt-oss をモデル カタログからダウンロード、CLI でのアクセス、REST API でアクセスする例を紹介します。なお、REST API でのアクセスにおいては、Foundry Local C# SDK, OpenAI .NET API library を使用した C# コンソール アプリでの実装例を説明します。

Foundry Local のインストール

winget で Foundry Local を Windows にインストールできます。

winget install Microsoft.FoundryLocal

既に Foundry Local をインストールしていて、最新版に更新する場合は以下のコマンドを実行します。

winget update Microsoft.FoundryLocal

Foundry Local によるモデルのダウンロード

ここからは、Foundry Local CLI を使用してコマンドラインから操作を行います。

Foundry Local で利用可能なモデルの一覧を表示します。

foundry model list

現在(2025 年 9 月) では、Phi シリーズや Deepseek、Qwen が利用できるようです。

Alias                          Device     Task               File Size    License      Model ID
-----------------------------------------------------------------------------------------------
phi-4                          GPU        chat-completion    8.37 GB      MIT          Phi-4-cuda-gpu
                               GPU        chat-completion    8.37 GB      MIT          Phi-4-generic-gpu
                               CPU        chat-completion    10.16 GB     MIT          Phi-4-generic-cpu
--------------------------------------------------------------------------------------------------------
phi-3-mini-128k                GPU        chat-completion    2.13 GB      MIT          Phi-3-mini-128k-instruct-cuda-gpu
                               GPU        chat-completion    2.13 GB      MIT          Phi-3-mini-128k-instruct-generic-gpu
                               CPU        chat-completion    2.54 GB      MIT          Phi-3-mini-128k-instruct-generic-cpu
---------------------------------------------------------------------------------------------------------------------------
phi-3-mini-4k                  GPU        chat-completion    2.13 GB      MIT          Phi-3-mini-4k-instruct-cuda-gpu
                               GPU        chat-completion    2.13 GB      MIT          Phi-3-mini-4k-instruct-generic-gpu
                               CPU        chat-completion    2.53 GB      MIT          Phi-3-mini-4k-instruct-generic-cpu
-------------------------------------------------------------------------------------------------------------------------
mistral-7b-v0.2                GPU        chat-completion    3.98 GB      apache-2.0   mistralai-Mistral-7B-Instruct-v0-2-cuda-gpu
                               GPU        chat-completion    4.07 GB      apache-2.0   mistralai-Mistral-7B-Instruct-v0-2-generic-gpu
                               CPU        chat-completion    4.07 GB      apache-2.0   mistralai-Mistral-7B-Instruct-v0-2-generic-cpu
-------------------------------------------------------------------------------------------------------------------------------------
phi-3.5-mini                   GPU        chat-completion    2.13 GB      MIT          Phi-3.5-mini-instruct-cuda-gpu
                               GPU        chat-completion    2.16 GB      MIT          Phi-3.5-mini-instruct-generic-gpu
                               CPU        chat-completion    2.53 GB      MIT          Phi-3.5-mini-instruct-generic-cpu
------------------------------------------------------------------------------------------------------------------------
deepseek-r1-14b                GPU        chat-completion    9.83 GB      MIT          deepseek-r1-distill-qwen-14b-cuda-gpu
                               GPU        chat-completion    10.27 GB     MIT          deepseek-r1-distill-qwen-14b-generic-gpu
                               CPU        chat-completion    11.51 GB     MIT          deepseek-r1-distill-qwen-14b-generic-cpu
-------------------------------------------------------------------------------------------------------------------------------
deepseek-r1-7b                 GPU        chat-completion    5.28 GB      MIT          deepseek-r1-distill-qwen-7b-cuda-gpu
                               GPU        chat-completion    5.58 GB      MIT          deepseek-r1-distill-qwen-7b-generic-gpu
                               CPU        chat-completion    6.43 GB      MIT          deepseek-r1-distill-qwen-7b-generic-cpu
------------------------------------------------------------------------------------------------------------------------------
qwen2.5-0.5b                   GPU        chat-completion    0.52 GB      apache-2.0   qwen2.5-0.5b-instruct-cuda-gpu
                               GPU        chat-completion    0.68 GB      apache-2.0   qwen2.5-0.5b-instruct-generic-gpu
                               CPU        chat-completion    0.80 GB      apache-2.0   qwen2.5-0.5b-instruct-generic-cpu
------------------------------------------------------------------------------------------------------------------------
qwen2.5-1.5b                   GPU        chat-completion    1.25 GB      apache-2.0   qwen2.5-1.5b-instruct-cuda-gpu
                               GPU        chat-completion    1.51 GB      apache-2.0   qwen2.5-1.5b-instruct-generic-gpu
                               CPU        chat-completion    1.78 GB      apache-2.0   qwen2.5-1.5b-instruct-generic-cpu
------------------------------------------------------------------------------------------------------------------------
qwen2.5-coder-7b               GPU        chat-completion    4.73 GB      apache-2.0   qwen2.5-coder-7b-instruct-cuda-gpu
                               GPU        chat-completion    4.73 GB      apache-2.0   qwen2.5-coder-7b-instruct-generic-gpu
                               CPU        chat-completion    6.16 GB      apache-2.0   qwen2.5-coder-7b-instruct-generic-cpu
----------------------------------------------------------------------------------------------------------------------------
qwen2.5-coder-0.5b             GPU        chat-completion    0.52 GB      apache-2.0   qwen2.5-coder-0.5b-instruct-cuda-gpu
                               GPU        chat-completion    0.52 GB      apache-2.0   qwen2.5-coder-0.5b-instruct-generic-gpu
                               CPU        chat-completion    0.80 GB      apache-2.0   qwen2.5-coder-0.5b-instruct-generic-cpu
------------------------------------------------------------------------------------------------------------------------------
qwen2.5-coder-1.5b             GPU        chat-completion    1.25 GB      apache-2.0   qwen2.5-coder-1.5b-instruct-cuda-gpu
                               GPU        chat-completion    1.25 GB      apache-2.0   qwen2.5-coder-1.5b-instruct-generic-gpu
                               CPU        chat-completion    1.78 GB      apache-2.0   qwen2.5-coder-1.5b-instruct-generic-cpu
------------------------------------------------------------------------------------------------------------------------------
phi-4-mini                     GPU        chat-completion    3.60 GB      MIT          Phi-4-mini-instruct-cuda-gpu
                               GPU        chat-completion    3.72 GB      MIT          Phi-4-mini-instruct-generic-gpu
                               CPU        chat-completion    4.80 GB      MIT          Phi-4-mini-instruct-generic-cpu
----------------------------------------------------------------------------------------------------------------------
phi-4-mini-reasoning           GPU        chat-completion    3.15 GB      MIT          Phi-4-mini-reasoning-cuda-gpu
                               GPU        chat-completion    3.15 GB      MIT          Phi-4-mini-reasoning-generic-gpu
                               CPU        chat-completion    4.52 GB      MIT          Phi-4-mini-reasoning-generic-cpu
-----------------------------------------------------------------------------------------------------------------------
qwen2.5-14b                    GPU        chat-completion    8.79 GB      apache-2.0   qwen2.5-14b-instruct-cuda-gpu
                               CPU        chat-completion    11.06 GB     apache-2.0   qwen2.5-14b-instruct-generic-cpu
-----------------------------------------------------------------------------------------------------------------------
qwen2.5-7b                     GPU        chat-completion    4.73 GB      apache-2.0   qwen2.5-7b-instruct-cuda-gpu
                               GPU        chat-completion    5.20 GB      apache-2.0   qwen2.5-7b-instruct-generic-gpu
                               CPU        chat-completion    6.16 GB      apache-2.0   qwen2.5-7b-instruct-generic-cpu
----------------------------------------------------------------------------------------------------------------------
qwen2.5-coder-14b              GPU        chat-completion    8.79 GB      apache-2.0   qwen2.5-coder-14b-instruct-cuda-gpu
                               GPU        chat-completion    8.79 GB      apache-2.0   qwen2.5-coder-14b-instruct-generic-gpu
                               CPU        chat-completion    11.06 GB     apache-2.0   qwen2.5-coder-14b-instruct-generic-cpu
-----------------------------------------------------------------------------------------------------------------------------
gpt-oss-20b                    GPU        chat-completion    9.65 GB      apache-2.0   gpt-oss-20b-cuda-gpu

gpt-oss においては、210 億パラメータの gpt-oss-20b が利用できます。(VRAM 16 GB 以上の Nvidia GPU が必要。)
gpt-oss-20b-cuda-gpu をダウンロードします。ファイルサイズは、9.65 GB です。

foundry model download gpt-oss-20b-cuda-gpu

ダウンロードしたモデルは、キャッシュに登録されます。 以下のコマンドを実行します。

foundry cache list

ローカルにダウンロード済のモデルが一覧されます。

Models cached on device:
   Alias                         Model ID
💾 gpt-oss-20b                   gpt-oss-20b-cuda-gpu
💾 phi-4-mini                    Phi-4-mini-instruct-cuda-gpu
💾 phi-4                         Phi-4-cuda-gpu

Foundry Local CLI によるモデルの実行

ダウンロードしてキャッシュに登録されたモデルのエイリアス、もしくはモデル ID を指定してチャットを起動します。
今回は、モデルのエイリアス gpt-oss-20b を指定してチャットを起動します。
指定したモデルがキャッシュにない場合は、ダウンロードが開始して、ダウンロード後にチャットが起動します。

foundry model run gpt-oss-20b

"Interactive mode, please enter your prompt" と表示されたら、任意のメッセージを入力して [Enter] を選択します。

Model gpt-oss-20b-cuda-gpu was found in the local cache.
🕚 Loading model...
🟢 Model gpt-oss-20b-cuda-gpu loaded successfully

Interactive Chat. Enter /? or /help for help.

Interactive mode, please enter your prompt
> こんにちは [Enter]

入力したメッセージに対する回答が生成されます。

🤖 <|channel|>analysis<|message|>Need respond greeting.<|end|><|start|>assistant<|channel|>final<|message|>こんにちは!今日はどんなことでお手伝いできそうですか?<|return|>

以下は、Phi4-mini の回答生成の出力ですが、比較すると、gpt-oss の場合は、特殊なフォーマットで出力されていることが分かります。

Model Phi-4-mini-instruct-cuda-gpu was found in the local cache.
Unloading existing models. Use --retain true to keep additional models loaded.
🕑 Loading model...
🟢 Model Phi-4-mini-instruct-cuda-gpu loaded successfully

Interactive Chat. Enter /? or /help for help.

Interactive mode, please enter your prompt
> こんにちは
🤖 この指示は、ユーザーが「こんにちは」という挨拶を送ることを示しています。これは、一般的な挨拶であり、他の人と会話を始めるためや、誰かに会ったときに挨拶するために使用されます。
>

Foundry Local における gpt-oss の回答は、Harmony Response Format でフォーマットされて出力されています。

対話を終了する場合は、/exit で終了できます。

Interactive mode, please enter your prompt
> /exit[Enter]

Foundry Local C# SDK, OpenAI .NET API library による Foundry Local モデルの実行

ここからは、C# コードによる Foundry Local の操作、モデルへのアクセス方法を説明します。

Foundry Local の操作は、Foundry Local SDK を使用します。

Foundry Local C# SDK は、Microsoft.AI.Foundry.Local パッケージとして、Nuget に公開されています。

Foundry Local では、OpenAI chat completion API 互換 API が提供されています。

よって、Foundry Local モデルへのアクセスには、OpenAI .NET API library が利用できます。

Nuget パッケージのインストール

C# コンソール プロジェクトに、Foundry Local SDK, OpenAI .NET API library をインストールします。

dotnet add package Microsoft.AI.Foundry.Local --version 0.2.0
dotnet add package OpenAI --version 2.4.0

Foundry Local の操作

Foundry Local の操作を行うには、FoundryLocalManager のインスタンスを生成します。

FoundryLocalManager foundryLocalManager = new FoundryLocalManager();

if (!foundryLocalManager.IsServiceRunning)
{
	// Foundry Local サービスが停止している場合は、サービスを起動
	await foundryLocalManager.StartServiceAsync(cancellationToken);
}

FoundryLocalManager のインスタンスからサービスの実行状態の確認、開始、終了ができます。

FoundryLocalManager のインスタンスから Foundry Local CLI で提供されている各種操作ができます。
ダウンロード済モデルの一覧を取得します。

// 使用するモデルのエイリアスまたはモデル ID を指定
string modelName = "gpt-oss-20b";

// キャッシュされているモデルの一覧を取得
List<ModelInfo> cachedModels = await foundryLocalManager.ListCachedModelsAsync(cancellationToken);

foreach (ModelInfo cachedModel in cachedModels)
{
	Console.WriteLine($"Model Alias: {cachedModel.Alias}, Model ID: {cachedModel.ModelId}");
}

// エイリアスまたはモデルIDに一致するモデルを取得
ModelInfo? modelInfo = cachedModels.FirstOrDefault(m => m.Alias == modelName || m.ModelId == modelName);

if (modelInfo == null)
{
	Console.WriteLine($"Model with alias or ID '{modelName}' not found in cached models.");
	return;
}

// 選択されたモデルの情報を表示
Console.WriteLine($"Selected Model Alias: {modelInfo.Alias}, Model ID: {modelInfo.ModelId}");

モデルへのアクセスに必要なエンドポイント、API キーを取得します。

// Fondry Local のエンドポイントを取得
Uri endpoint = foundryLocalManager.Endpoint;
// Foundry Local の API キーを取得
string apiKey = foundryLocalManager.ApiKey;
// モデル ID を取得
string modelId = modelInfo.ModelId;

モデルへのアクセス

OpenAI .NET API library を使用して gpt-oss モデルへのアクセスを行います。
FoundryLocalManager インスタンスから取得した API キー、エンドポイントを使用して OpenAIClient を初期化します。 また、モデル一覧から取得した gpt-oss のモデル ID で、ChatClient を取得します。

// OpenAI クライアントを初期化
OpenAIClient client = new(new ApiKeyCredential(apiKey), new OpenAIClientOptions
{
	Endpoint = endpoint,
});

// チャットクライアントを取得
ChatClient chatClient = client.GetChatClient(modelId);

標準入力からユーザー メッセージを取得します。

// システムプロンプト
string systemPrompt = "ユーザーの質問に日本語で回答してください。";

// ユーザー メッセージを取得
Console.WriteLine("Enter your message:");
string? userPrompt = Console.ReadLine();

ChatMessage[] messages = [new SystemChatMessage(systemPrompt), new UserChatMessage(userPrompt)];

モデルへメッセージを送信し、ストリームで受信します。

string? currentChannel = null;
string? currentRole = null;
bool isMessageStarting = false;
int outputMillisecondsDelay = 33;

// ストリーミングでチャットメッセージを送信
await foreach (var update in chatClient.CompleteChatStreamingAsync(messages, cancellationToken: cancellationToken))
{
	if (update.ContentUpdate.Count > 0)
	{
		if (update.ContentUpdate[0].Text == "<|channel|>")
		{
			// 次のループでチャネル名を取得する
			currentChannel = "WAITING_FOR_OUTPUT";
		}
		else if (currentChannel == "WAITING_FOR_OUTPUT")
		{
			// チャネル名の取得
			currentChannel = update.ContentUpdate[0].Text;
			Console.WriteLine($"Channel: {currentChannel}");
		}
		if (update.ContentUpdate[0].Text == "<|start|>")
		{
			// 次のループでチャネル名を取得する
			currentRole = "WAITING_FOR_OUTPUT";
		}
		else if (currentRole == "WAITING_FOR_OUTPUT")
		{
			// チャネル名の取得
			currentRole = update.ContentUpdate[0].Text;
			Console.WriteLine($"Role: {currentRole}");
		}
		else if (update.ContentUpdate[0].Text == "<|message|>")
		{
			// メッセージ開始
			isMessageStarting = true;
			Console.WriteLine("Message:");
		}
		else if (update.ContentUpdate[0].Text == "<|end|>" || update.ContentUpdate[0].Text == "<|return|>")
		{
			// メッセージ終了
			isMessageStarting = false;
			currentRole = null;
			Console.WriteLine("\n[End of Message.]\n\n");

			if (update.ContentUpdate[0].Text == "<|return|>")
			{
				// 終了
				break;
			}
		}				
		else if (isMessageStarting)
		{
			// メッセージの出力
			Console.Write(update.ContentUpdate[0].Text);
		}
	}

	await Task.Delay(outputMillisecondsDelay);
}

現在 Foundry Local では、Responses API が提供されていないため、Chat Completion API を使用してメッセージを送信しています。また、モデルからの応答は、OpenAI Harmony Response Format で出力されるため条件分岐でパーズを行っています。OpenAI Harmony Response Format への最小限の対応をしています。

OpenAI から OpenAI Harmony Response Format ライブラリが提供されていますが、現状では、C# 向けのライブラリは提供されていないようです。

コード例

ここまでの一連のコードは以下となります。

using Microsoft.AI.Foundry.Local;
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;

namespace FoundryLocalDemo001;

internal class Program
{
    static async Task Main(string[] args)
    {
		// 使用するモデルのエイリアスまたはモデル ID を指定
		string modelName = "gpt-oss-20b";
		// システムプロンプト
		string systemPrompt = "ユーザーの質問に日本語で回答してください。";

		CancellationTokenSource cancellationTokenSource = new();
		CancellationToken cancellationToken = cancellationTokenSource.Token;

		FoundryLocalManager foundryLocalManager = new FoundryLocalManager();

		if (!foundryLocalManager.IsServiceRunning)
		{
			// Foundry Local サービスが停止している場合は、サービスを起動
			await foundryLocalManager.StartServiceAsync(cancellationToken);
		}

		// キャッシュされているモデルの一覧を取得
		List<ModelInfo> cachedModels = await foundryLocalManager.ListCachedModelsAsync(cancellationToken);

		foreach (ModelInfo cachedModel in cachedModels)
		{
			Console.WriteLine($"Model Alias: {cachedModel.Alias}, Model ID: {cachedModel.ModelId}");
		}

		// エイリアスまたはモデルIDに一致するモデルを取得
		ModelInfo? modelInfo = cachedModels.FirstOrDefault(m => m.Alias == modelName || m.ModelId == modelName);

		if (modelInfo == null)
		{
			Console.WriteLine($"Model with alias or ID '{modelName}' not found in cached models.");
			return;
		}

		// 選択されたモデルの情報を表示
		Console.WriteLine($"Selected Model Alias: {modelInfo.Alias}, Model ID: {modelInfo.ModelId}");


		// Fondry Local のエンドポイントを取得
		Uri endpoint = foundryLocalManager.Endpoint;
		// Foundry Local の API キーを取得
		string apiKey = foundryLocalManager.ApiKey;
		// モデル ID を取得
		string modelId = modelInfo.ModelId;

		// OpenAI クライアントを初期化
		OpenAIClient client = new(new ApiKeyCredential(apiKey), new OpenAIClientOptions
		{
			Endpoint = endpoint,
			NetworkTimeout = TimeSpan.FromMinutes(10)
		});

		// チャットクライアントを取得
		ChatClient chatClient = client.GetChatClient(modelId);

		// ユーザー メッセージを取得
		Console.WriteLine("Enter your message:");
		string? userPrompt = Console.ReadLine();

		ChatMessage[] messages = [new SystemChatMessage(systemPrompt), new UserChatMessage(userPrompt)];

		string? currentChannel = null;
		string? currentRole = null;
		bool isMessageStarting = false;
		int outputMillisecondsDelay = 33;

		// ストリーミングでチャットメッセージを送信
		await foreach (var update in chatClient.CompleteChatStreamingAsync(messages, cancellationToken: cancellationToken))
		{
			if (update.ContentUpdate.Count > 0)
			{
				if (update.ContentUpdate[0].Text == "<|channel|>")
				{
					// 次のループでチャネル名を取得する
					currentChannel = "WAITING_FOR_OUTPUT";
				}
				else if (currentChannel == "WAITING_FOR_OUTPUT")
				{
					// チャネル名の取得
					currentChannel = update.ContentUpdate[0].Text;
					Console.WriteLine($"Channel: {currentChannel}");
				}
				if (update.ContentUpdate[0].Text == "<|start|>")
				{
					// 次のループでチャネル名を取得する
					currentRole = "WAITING_FOR_OUTPUT";
				}
				else if (currentRole == "WAITING_FOR_OUTPUT")
				{
					// チャネル名の取得
					currentRole = update.ContentUpdate[0].Text;
					Console.WriteLine($"Role: {currentRole}");
				}
				else if (update.ContentUpdate[0].Text == "<|message|>")
				{
					// メッセージ開始
					isMessageStarting = true;
					Console.WriteLine("Message:");
				}
				else if (update.ContentUpdate[0].Text == "<|end|>" || update.ContentUpdate[0].Text == "<|return|>")
				{
					// メッセージ終了
					isMessageStarting = false;
					currentRole = null;
					Console.WriteLine("\n[End of Message.]\n\n");

					if (update.ContentUpdate[0].Text == "<|return|>")
					{
						// 終了
						break;
					}
				}				
				else if (isMessageStarting)
				{
					// メッセージの出力
					Console.Write(update.ContentUpdate[0].Text);
				}
			}

			await Task.Delay(outputMillisecondsDelay);
		}		
	}
}

実行結果

コードの実行結果は、以下となります。

Model Alias: gpt-oss-20b, Model ID: gpt-oss-20b-cuda-gpu
Model Alias: phi-4-mini, Model ID: Phi-4-mini-instruct-cuda-gpu
Model Alias: phi-4, Model ID: Phi-4-cuda-gpu
Selected Model Alias: gpt-oss-20b, Model ID: gpt-oss-20b-cuda-gpu
Enter your message:
こんにちは
Channel: analysis
Message:
User: "こんにちは" User wants response in Japanese. Provide greeting.
[End of Message.]


Role: assistant
Channel: final
Message:
こんにちは!いかがお過ごしですか?

何でも聞いてくださいね。
[End of Message.]

analysis チャネルでの出力が、モデルでの CoT(chain of thought) のメッセージとなります。システム メッセージを加味した思考が行われていることが確認できます。
final チャネルでの出力が、エンドユーザーへ表示するメッセージとなります。ユーザーメッセージに対する応答としては、こちらのメッセージを使用します。

まとめ

Foundry Local のセットアップから、CLI での操作、C# による gpt-oss モデル実行の実装例を説明しました。Foundry Local のセットアップは容易で、すぐにローカルでのモデル実行環境を構築することができました。Foundry Local SDK もシンプルで容易に Foundry Local を操作できます。モデル実行においては、OpenAI が提供する API との互換により、クラウドでの知識、資産の再利用ができます。

現状の課題としては、モデル カタログで提供されているモデルが限られているため、今後の充実を期待します。

また、応答性能の改善にも期待します。これは、Foundry Local に起因するところは少なく、gpt-oss-20b が比較的大規模であり、今回使用した PC の GPU スペックに依存する部分が多いとは思いますので、ソフトウェア的な改善は難しいかもしれません。今回使用した PC の GPU は、NVIDIA GeForce GTX 1650 Ti を積んでいますが、gpt-oss-20b での「こんにちは」の回答生成に、20 秒程かかってしまいました。NVIDIA GeForce GTX 1650 Ti は、数世代前の GPU であり、現行の GPU を使用すれば大幅な性能改善が見込まれますが、現行でもエントリーモデルの PC はオンチップの GPU がほとんどということを考えると、普段使いの PC で、ローカルで言語モデルを実行というシナリオはまだ先の話かもしれません。現状では、高スペックな GPU を積んだ PC や、エッジサーバー経由での実行が現実的なシナリオと考えられます。

使用するハードウェア性能の問題はありますが、容易にローカルでの言語モデルを実行環境を構築でき、クラウドと同様の手法でモデルへアクセス可能な Foundry Local の今後の進化に期待しています。

コメント (0)

コメントの投稿