0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Phi Silica AIを使用する。

0
Posted at

はじめに

Copilot+ PC に搭載されているNPUを活用し、ローカルで動作する小規模言語モデル(SLM)である Phi Silica を C# から呼び出すアプリを作成しました。

公式ドキュメント通りに進めてもいくつかハマるポイント(インストール設定・コンパイルエラー・日本語応答が空欄になる問題・応答が遅い問題など)があったため、備忘録として解決策をまとめます。


1. 開発環境

Phi Silicaを動かすには以下の環境が必要です。

項目 要件
ハードウェア NPU搭載の Copilot+ PC(Snapdragon X / Intel Core Ultra 200V / AMD Ryzen AI 300 等)
OS Windows 11 25H2(ビルド 26100 以降)。winver コマンドで確認
開発者モード Windows 設定 → システム → 開発者向け → 開発者モード をオン
IDE Visual Studio 2026(「Windows アプリケーション開発」ワークロードをインストール)
フレームワーク .NET 10
NuGet パッケージ Microsoft.WindowsAppSDK バージョン 2.0.0-preview1 または 2.0.0-experimental6

Experimental版を使う理由(LAFトークン回避)

通常版(Stable版)のSDKで Phi Silica API を使用するには、Microsoft に LAF(Limited Access Feature)トークン を申請して承認を得る必要があります [1]。承認には数週間かかるケースもあるため、開発・検証目的であれば LAFトークンチェックが意図的にスキップされている Experimental版2.0.0-preview1 または 2.0.0-experimental6)の使用を強く推奨します [2]。

公式ドキュメントより
"At this time, we recommend using experimental releases as they do not require LAF tokens."


2. プロジェクトの作成とセットアップ

Step 1:プロジェクトの新規作成

  1. Visual Studio を起動し、「新しいプロジェクトの作成」 を選択します。
  2. 検索ボックスに WinUI と入力し、「空白のアプリ、パッケージ (WinUI 3 in Desktop)」 を選択します。
  3. プロジェクト名(例: PhiSilicaChat)と保存場所を設定して 「作成」 をクリックします。

Step 2:NuGet パッケージのインストール

  1. ソリューション エクスプローラーでプロジェクトを右クリックし、「NuGet パッケージの管理」 を選択します。
  2. 「プレリリースを含める」 にチェックを入れます。
  3. 検索ボックスに Microsoft.WindowsAppSDK と入力し、バージョン 2.0.0-preview1(または 2.0.0-experimental6)を選択して 「インストール」 をクリックします。

Step 3:ビルド構成の変更

ツールバーのドロップダウンで、ビルド構成を AnyCPU から x64 または ARM64 に変更します。AnyCPU では Phi Silica API は動作しません。

また、起動プロファイルのドロップダウン(再生ボタンの隣)で 「プロジェクト名 (Package)」(パッケージ版)を選択します。

重要: LanguageModel API は MSIXパッケージとして実行 する必要があります。「Unpackaged(パッケージなし)」プロファイルで実行すると GetReadyState() が COM エラーで失敗します。必ずパッケージ版プロファイルを使用してください。

Step 4:Package.appxmanifest の編集

※自分が試した時はプロジェクトを新規作成時に設定されていました。
ソリューション エクスプローラーで Package.appxmanifest を右クリックし、「コードの表示」 を選択して直接編集します。

<Package> 開始タグに xmlns:systemai 名前空間を追加する

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10"
  IgnorableNamespaces="uap rescap systemai">

注意: xmlns:systemai の宣言は必ず <Package> 開始タグ内に記述してください。<Capabilities> 内に <systemai:Capability> を追加する前にこの宣言がないと「宣言されていないプレフィックス」エラーになります。

<Dependencies>MinVersion10.0.26100.0 に変更する

<Dependencies>
  <TargetDeviceFamily Name="Windows.Universal"
                      MinVersion="10.0.26100.0"
                      MaxVersionTested="10.0.26300.0" />
  <TargetDeviceFamily Name="Windows.Desktop"
                      MinVersion="10.0.26100.0"
                      MaxVersionTested="10.0.26300.0" />
</Dependencies>

MinVersion が低いと systemai:Capability が無視され、GetReadyState() が「アプリによって宣言されていません」エラーになります。

<Capabilities>systemAIModels を追加する

<Capabilities>
  <rescap:Capability Name="runFullTrust"/>
  <systemai:Capability Name="systemAIModels"/>
</Capabilities>

Step 5:.csproj の編集

Visual Studio がビルド時にマニフェストのバージョン設定を上書きしないよう、.csproj に以下を追加します。

ソリューション エクスプローラーでプロジェクト名(C# アイコンのノード)を右クリックし、「プロジェクト ファイルの編集」 を選択します。

<PropertyGroup>
  <!-- 既存の設定はそのままにして、以下の2行を追加する -->
  <AppxOSMinVersionReplaceManifestVersion>false</AppxOSMinVersionReplaceManifestVersion>
  <AppxOSMaxVersionTestedReplaceManifestVersion>false</AppxOSMaxVersionTestedReplaceManifestVersion>
</PropertyGroup>

3. ハマりどころ①:API変更によるコンパイルエラー

ネット上の古い記事を参考にすると、以下のエラーが出ることがあります。

error CS0117: 'AIFeatureReadyState' に 'EnsureNeeded' の定義がありません
error CS0234: 型または名前空間の名前 'PackageDeploymentStatus' が存在しません

原因と解決策

Windows App SDK のバージョンアップに伴い、列挙値と戻り値の型が変更されています。

旧API(誤) 新API(正)
AIFeatureReadyState.EnsureNeeded AIFeatureReadyState.NotReady
PackageDeploymentStatus.CompletedSuccess AIFeatureReadyResultState.Success

修正後の正しいコード

if (LanguageModel.GetReadyState() == AIFeatureReadyState.NotReady)
{
    var result = await LanguageModel.EnsureReadyAsync();
    if (result.Status != AIFeatureReadyResultState.Success) return;
}

AIFeatureReadyState の現在の正しい列挙値は以下の4つです。

意味
Ready モデルがインストール済みで使用可能
NotReady 未インストール。EnsureReadyAsync() で準備可能
NotSupportedOnCurrentSystem このOSまたはハードウェアでは非対応
DisabledByUser ユーザーが無効化している

4. ハマりどころ②:日本語プロンプトで応答が「空欄」になる

モデルの初期化に成功し、英語("hello" など)では応答が返ってくるのに、日本語のプロンプトを投げると response.Text が空文字列になるという現象に遭遇しました。

原因:コンテンツフィルターの誤検知

Phi Silica には有害な出力を防ぐためのコンテンツモデレーション機能が組み込まれています。しかし、このフィルターは英語ベースで訓練されているため、日本語テキストを誤検知してブロックしてしまうことが多いようです [3]。

GenerateResponseAsync() はブロックされても例外を投げず、単に空文字列を返す仕様になっています。まずは response.Status を確認することで原因を特定できます。

switch (response.Status)
{
    case LanguageModelResponseStatus.Complete:
        OutputText.Text = response.Text;
        break;
    case LanguageModelResponseStatus.PromptBlockedByContentModeration:
        OutputText.Text = "【ブロック】プロンプトがフィルターに引っかかりました";
        break;
    case LanguageModelResponseStatus.ResponseBlockedByContentModeration:
        OutputText.Text = "【ブロック】応答がフィルターに引っかかりました";
        break;
    default:
        OutputText.Text = $"【エラー】Status = {response.Status}";
        break;
}

解決策:フィルターレベルを Minimum に下げ、英語のシステムプロンプトを前置する

var options = new LanguageModelOptions();
var filterOptions = new ContentFilterOptions();

// プロンプト側・応答側の全カテゴリを最小感度に設定
filterOptions.PromptMaxAllowedSeverityLevel.Violent  = SeverityLevel.Minimum;
filterOptions.PromptMaxAllowedSeverityLevel.Hate     = SeverityLevel.Minimum;
filterOptions.PromptMaxAllowedSeverityLevel.SelfHarm = SeverityLevel.Minimum;
filterOptions.PromptMaxAllowedSeverityLevel.Sexual   = SeverityLevel.Minimum;
filterOptions.ResponseMaxAllowedSeverityLevel.Violent  = SeverityLevel.Minimum;
filterOptions.ResponseMaxAllowedSeverityLevel.Hate     = SeverityLevel.Minimum;
filterOptions.ResponseMaxAllowedSeverityLevel.SelfHarm = SeverityLevel.Minimum;
filterOptions.ResponseMaxAllowedSeverityLevel.Sexual   = SeverityLevel.Minimum;

options.ContentFilterOptions = filterOptions;

// 英語のシステムプロンプトを前置して日本語応答を要求する
string prompt = $"You are a helpful assistant. Always respond in Japanese.\n\nUser: {userPrompt}\n\nAssistant:";

5. ハマりどころ③:応答に20秒以上かかる

日本語の出力を得ることに成功しましたが、短い応答でも 20秒近く待たされる ことがありました。

原因

GitHub Issue でも「テキスト生成が異常に遅い」という報告が多数挙がっています [4]。主な原因は以下の通りです。

  • 初回実行のウォームアップ: CreateAsync() 直後の最初の推論はモデルのロードで遅い。
  • 日本語のトークン消費: 日本語は英語に比べてトークン数が多く、NPUでのコンテキスト処理に時間がかかる。

解決策:ストリーミング応答(Progress ハンドラ)の実装

処理時間そのものを短縮するのは難しいですが、ストリーミングAPI を使ってトークンが生成されるたびにUIを更新することで、ChatGPTのような「文字が流れてくる」UXになり、体感の待ち時間を大幅に削減できます [5]。

GenerateResponseAsync()IAsyncOperationWithProgress<LanguageModelResponseResult, string> を返します。await する前に Progress ハンドラを設定するのがポイントです。


6. 完成版コード(ストリーミング対応)

以下が今回のハマりどころをすべて解決した完成版コードです。

using Microsoft.Windows.AI.ContentModeration;
using Microsoft.Windows.AI.Generative;
using Windows.Foundation;

// モデルはフィールドに保持し、2回目以降の推論を高速化する
// ※ using を使うとメソッド終了時に破棄されてしまうため NG
private LanguageModel? _languageModel;

/// <summary>
/// アプリ起動時に呼び出してモデルを初期化する
/// </summary>
private async void InitAI()
{
    // モデルのインストール状態を確認
    if (LanguageModel.GetReadyState() == AIFeatureReadyState.NotReady)
    {
        var result = await LanguageModel.EnsureReadyAsync();
        if (result.Status != AIFeatureReadyResultState.Success)
        {
            StatusText.Text = "モデルの準備に失敗しました。";
            return;
        }
    }

    _languageModel = await LanguageModel.CreateAsync();
    StatusText.Text = "モデル準備完了";
}

/// <summary>
/// プロンプトをストリーミングで送信し、トークンが届くたびにUIを更新する
/// </summary>
private async Task SendPromptStreamingAsync(string userPrompt)
{
    if (_languageModel == null) return;

    // ① コンテンツフィルターを最小感度に設定(日本語の誤検知対策)
    var options = new LanguageModelOptions();
    var filterOptions = new ContentFilterOptions();
    filterOptions.PromptMaxAllowedSeverityLevel.Violent  = SeverityLevel.Minimum;
    filterOptions.PromptMaxAllowedSeverityLevel.Hate     = SeverityLevel.Minimum;
    filterOptions.PromptMaxAllowedSeverityLevel.SelfHarm = SeverityLevel.Minimum;
    filterOptions.PromptMaxAllowedSeverityLevel.Sexual   = SeverityLevel.Minimum;
    filterOptions.ResponseMaxAllowedSeverityLevel.Violent  = SeverityLevel.Minimum;
    filterOptions.ResponseMaxAllowedSeverityLevel.Hate     = SeverityLevel.Minimum;
    filterOptions.ResponseMaxAllowedSeverityLevel.SelfHarm = SeverityLevel.Minimum;
    filterOptions.ResponseMaxAllowedSeverityLevel.Sexual   = SeverityLevel.Minimum;
    options.ContentFilterOptions = filterOptions;

    // ② 英語のシステムプロンプトを前置して日本語応答を安定させる
    string prompt = $"You are a helpful assistant. Always respond in Japanese.\n\nUser: {userPrompt}\n\nAssistant:";

    // ③ UIをクリアして送信中状態にする
    ResponseText.Text = string.Empty;
    StatusText.Text = "応答を生成中...";
    SendButton.IsEnabled = false;

    // ④ await せずに asyncOp を取得する(ここがストリーミングのポイント)
    var asyncOp = _languageModel.GenerateResponseAsync(prompt, options);

    // ⑤ トークンが生成されるたびに呼ばれる Progress ハンドラを設定する
    asyncOp.Progress = (op, token) =>
    {
        // Progress ハンドラはバックグラウンドスレッドで呼ばれるため、
        // UIスレッドに切り替えてからテキストを追記する

        // WinUI 3 の場合:
        DispatcherQueue.TryEnqueue(() => { ResponseText.Text += token; });

        // WPF の場合は以下を使う:
        // Dispatcher.InvokeAsync(() => { ResponseText.Text += token; });
    };

    // ⑥ 完了を待機する
    var response = await asyncOp;

    // ⑦ ステータスを確認する(空欄の場合はここで原因がわかる)
    switch (response.Status)
    {
        case LanguageModelResponseStatus.Complete:
            StatusText.Text = "完了";
            break;
        case LanguageModelResponseStatus.PromptBlockedByContentModeration:
            ResponseText.Text = "【ブロック】プロンプトがコンテンツフィルターでブロックされました";
            StatusText.Text = "エラー";
            break;
        case LanguageModelResponseStatus.ResponseBlockedByContentModeration:
            ResponseText.Text = "【ブロック】応答がコンテンツフィルターでブロックされました";
            StatusText.Text = "エラー";
            break;
        default:
            ResponseText.Text = $"【エラー】Status = {response.Status}";
            StatusText.Text = "エラー";
            break;
    }

    SendButton.IsEnabled = true;
}

まとめ

Copilot+ PC の Phi Silica は完全オフライン・無料で動く強力なローカルAIですが、C#から扱う場合は以下の点に注意が必要です。

  1. Experimental版SDK を使ってLAFトークン要件を回避する。
  2. マニフェストと .csproj を正しく設定する(xmlns:systemai 名前空間・MinVersion 10.0.26100.0systemAIModels Capability・AppxOSMinVersionReplaceManifestVersion)。
  3. 古いAPI(EnsureNeeded / PackageDeploymentStatus)は最新の仕様に書き換える。
  4. 日本語プロンプトが空欄になる場合は、コンテンツフィルターを Minimum に下げ、英語のシステムプロンプトを前置する。
  5. 応答の遅延は ストリーミングAPI(asyncOp.Progress で体感速度を改善し、モデルインスタンスはフィールドに保持して使い回す。

サンプルコード

MainWindow.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="phiSilicaTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:phiSilicaTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="phiSilicaTest">

    <Window.SystemBackdrop>
        <MicaBackdrop />
    </Window.SystemBackdrop>

    <Grid>
        <TextBlock Name="abc" Text="Hello, World!" HorizontalAlignment="Center"  VerticalAlignment="Center" FontSize="24" TextWrapping="Wrap" />
    </Grid>
</Window>
MainWindow.xamal.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Microsoft.Windows.AI;
using Microsoft.Windows.AI.ContentSafety;
using Microsoft.Windows.AI.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Collections;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace phiSilicaTest
{
    /// <summary>
    /// An empty window that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            InitAI();
        }

        //   
        private LanguageModel? _languageModel;

        private async void InitAI()
        {
            // モデルのインストール状態を確認
            if (LanguageModel.GetReadyState() == AIFeatureReadyState.NotReady)
            {
                var result = await LanguageModel.EnsureReadyAsync();
                if (result.Status != AIFeatureReadyResultState.Success)
                {
                    abc.Text = "モデルの準備に失敗しました。";
                    return;
                }
            }

            // ★ using を使わずフィールドに保存(使い回すことで2回目以降が速くなる)
            _languageModel = await LanguageModel.CreateAsync();

            // モデル準備完了後にプロンプトを送信
            await SendPromptAsync("C#の非同期処理について簡潔に説明してください。");
        }

        private async Task SendPromptAsync(string userPrompt)
        {
            if (_languageModel == null) return;

            // コンテンツフィルターを最小に設定(日本語の誤検知対策)
            var options = new LanguageModelOptions();
            var filterOptions = new ContentFilterOptions();
            filterOptions.PromptMaxAllowedSeverityLevel.Violent = SeverityLevel.Minimum;
            filterOptions.PromptMaxAllowedSeverityLevel.Hate = SeverityLevel.Minimum;
            filterOptions.PromptMaxAllowedSeverityLevel.SelfHarm = SeverityLevel.Minimum;
            filterOptions.PromptMaxAllowedSeverityLevel.Sexual = SeverityLevel.Minimum;
            filterOptions.ResponseMaxAllowedSeverityLevel.Violent = SeverityLevel.Minimum;
            filterOptions.ResponseMaxAllowedSeverityLevel.Hate = SeverityLevel.Minimum;
            filterOptions.ResponseMaxAllowedSeverityLevel.SelfHarm = SeverityLevel.Minimum;
            filterOptions.ResponseMaxAllowedSeverityLevel.Sexual = SeverityLevel.Minimum;
            options.ContentFilterOptions = filterOptions;

            // 英語のシステムプロンプトを前置(日本語応答の安定性向上)
            string prompt = $"You are a helpful assistant. Always respond in Japanese.\n\nUser: {userPrompt}\n\nAssistant:";

            // ★ ストリーミング:await せずに asyncOp を取得
            abc.Text = string.Empty;  // 表示をクリア
            var asyncOp = _languageModel.GenerateResponseAsync(prompt, options);

            // ★ トークンが届くたびに UI に追記する
            asyncOp.Progress = async (op, token) =>
            {
                // WinUI 3 の場合(DispatcherQueue でUIスレッドに戻す)
                DispatcherQueue.TryEnqueue(() =>
                {
                    abc.Text += token;
                });

                // ★ WPF の場合は上記を以下に置き換える
                // await Dispatcher.InvokeAsync(() => { abc.Text += token; });
            };

            // ★ 完了を待つ
            var response = await asyncOp;

            // ステータス確認
            switch (response.Status)
            {
                case LanguageModelResponseStatus.Complete:
                    // 正常完了(abc.Text にはすでにストリーミングで内容が入っている)
                    break;
                case LanguageModelResponseStatus.ResponseBlockedByContentModeration:
                    abc.Text = "【ブロック】応答がコンテンツフィルターでブロックされました";
                    break;
                case LanguageModelResponseStatus.PromptBlockedByContentModeration:
                    abc.Text = "【ブロック】プロンプトがコンテンツフィルターでブロックされました";
                    break;
                default:
                    abc.Text = $"【エラー】Status = {response.Status}";
                    break;
            }
        }
    }
}

参考資料

[1] Microsoft Learn. "Get started with Phi Silica in the Windows App SDK." https://learn.microsoft.com/en-us/windows/ai/apis/phi-silica

[2] Microsoft Learn. "Windows AI API troubleshooting." https://learn.microsoft.com/en-us/windows/ai/apis/troubleshooting

[3] Microsoft Learn. "Content safety moderation with the Windows AI APIs." https://learn.microsoft.com/en-us/windows/ai/apis/content-moderation

[4] GitHub. "Phi Silica text completions are really slow #5334." https://github.com/microsoft/WindowsAppSDK/issues/5334

[5] Thomas Claudius Huber. "Use Windows AI in Your WPF Application." https://www.thomasclaudiushuber.com/2025/05/03/use-windows-ai-in-wpf/

[6] Microsoft Learn. "Tutorial: Build a chat app with Phi Silica and WinUI 3." https://learn.microsoft.com/en-us/windows/ai/apis/phi-silica-winui-tutorial

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?