シナプス技術者ブログ

シナプスの技術者公式ブログ。インターネットで、鹿児島の毎日を笑顔にします。

C#から生成AI(LLM)を使おう。

シナプスの技術部システム開発課の小園です。

生成AIの大規模言語モデル(LLM)といえば、Pythonのイメージが強いのですが、シナプスでは開発メイン言語としてC#を採用しており、そのC#からLLMをAPIで使える環境が出てきたので使ってみました。
とはいえ、Pythonの方がやっぱり環境が揃っていますので、使い分けが大事ではないかと思います。

環境

以下の環境で検証をしています。

  • Windows 10 Pro 22H2
  • .NET 9
  • Visual Studio 2022
  • LLM
    • OpenAI API
    • Google Gemini API

記事中で利用したNuGetパッケージ

記事中で利用したNuGetパッケージは以下コマンドでインストールしています。

dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.AI --prerelease
dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease
dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Prompty.Core --prerelease

実際にインストールされたパッケージは以下のバージョンになります。
私がインストールした 2025-03-07 時点の内容です。
Microsoft.Extensions.AI 系パッケージと Prompty.Core はプレビュー版またはアルファ版のため、今後のバージョンでAPIが変更になる可能性があります。

パッケージ バージョン
Azure.AI.OpenAI 2.1.0
Microsoft.Extensions.AI 9.3.0-preview.1.25114.11
Microsoft.Extensions.AI.OpenAI 9.3.0-preview.1.25114.11
Microsoft.Extensions.Configuration 9.0.2
Microsoft.Extensions.Configuration.UserSecrets 9.0.2
Microsoft.Extensions.Hosting 9.0.2
Prompty.Core 0.0.23-alpha

シークレット情報について

シークレット情報は、Microsoft.Extensions.Configuration.UserSecretsで管理しました。
以下のコマンドで登録しています。

dotnet user-secrets init
dotnet user-secrets set "DOTNET_ENVIRONMENT" "Development"
dotnet user-secrets set "OpenAI:ApiKey" "**********************************************"
dotnet user-secrets set "OpenAI:Model" "gpt-4o-mini"
dotnet user-secrets set "Gemini:ApiKey" "**********************************************"
dotnet user-secrets set "Gemini:Endpoint" "https://generativelanguage.googleapis.com/v1beta/openai/"
dotnet user-secrets set "Gemini:Model" "gemini-2.0-flash"
  • Microsoft.Extensions.Configuration.UserSecrets は開発用コンピュータ上の.NETのシークレット情報を管理する仕組みです。
    運用環境では、シークレット情報は環境変数に入れるなどの対策をしてください。
  • 各LLM(OpenAI API, Google Gemini API)のAPIキーは各々取得してください。

シンプルにLLMを呼ぶ

パッケージ OpenAI を使い、シンプルにLLMを呼びます。

コード

Program.cs

using Microsoft.Extensions.Configuration;
using OpenAI;
using OpenAI.Chat;

var config =
    new ConfigurationBuilder()
        .AddUserSecrets<Program>()
        .Build();

var client = new OpenAIClient(config["OpenAI:ApiKey"]).GetChatClient(config["OpenAI:Model"]);
var clientResult = client.CompleteChat([
    new UserChatMessage("AIとは何ですか?"),
]);

Console.WriteLine(clientResult.Value.Content[0].Text);
  • パッケージ OpenAI はOpenAI公式のクライアントライブラリです。
    先にインストールしたパッケージ Azure.AI.OpenAI が依存しているため、連動してインストールされています。
  • パッケージ Azure.AI.OpenAI はマイクロソフト公認の OpenAI API/Azure OpenAI API のクライアントライブラリです。

実行結果

AI(人工知能)とは、人工的に作られたシステムやプログラムが、人間のように知的な作業を行う能力を持つものを指します。AIは、機械学習、自然言語処理、画像認識、ロボティクスなど、さまざまな技術を用いて実現されます。

具体的には、AIは以下のような能力を持つことができます。

1. **データ分析**: 大量のデータからパターンやトレンドを検出し、予測を行う。
2. **自然言語処理**: 人間の言語を理解し、生成する能力(例:チャットボットや音声認識)。
3. **画像・音声認識**: 画像や音声を識別し、分類する能力(例:顔認識や音声アシスタント)。
4. **自動運転**: 車両が周囲の環境を認識し、自律的に運転する能力。

AIは、医療、金融、物流、エンターテインメントなど、さまざまな分野で活用されており、今後の技術革新によってその適用範囲はさらに広がると考えられています。

Microsoft.Extensions.AIを使う

パッケージ Microsoft.Extensions.AI を使います。

コード

Program.cs

using System.ClientModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenAI;

var app = new HostBuilder().Build();
var client = app.Services.GetRequiredService<IChatClient>();

await foreach (var update in client.GetStreamingResponseAsync(new List<ChatMessage>
                   { new(ChatRole.User, "AIとは何ですか?") }))
{
    Console.Write(update);
}

internal sealed class HostBuilder
{
    public IHost Build()
    {
        var config =
            new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddUserSecrets<Program>()
                .Build();
        var isDevelopment = config["DOTNET_ENVIRONMENT"] == "Development";

        var builder = Host.CreateApplicationBuilder();
        builder.Services.AddChatClient(isDevelopment ? CreateGeminiChatClient(config) : CreateOpenAiChatClient(config));

        return builder.Build();
    }

    IChatClient CreateGeminiChatClient(IConfigurationRoot config)
    {
        return
            new OpenAIClient(
                    new ApiKeyCredential(config["Gemini:ApiKey"]!),
                    new OpenAIClientOptions()
                    {
                        Endpoint = new Uri(config["Gemini:Endpoint"]!),
                    })
                .AsChatClient(config["Gemini:Model"]!);
    }

    IChatClient CreateOpenAiChatClient(IConfigurationRoot config)
    {
        return
            new OpenAIClient(
                    new ApiKeyCredential(config["OpenAI:ApiKey"]!))
                .AsChatClient(config["OpenAI:Model"]!);
    }
}
  • パッケージ Microsoft.Extensions.AI は様々なLLM(OpenAI/Azure OpenAI API/Ollama)を1つのインターフェース(IChatClient)で扱えるようにするパッケージです。
    扱えるLLMには以下のようなものがあります。
    • Ollamaは様々なローカルLLM/SLMを扱えます。
      Ollamaで利用できるLLM/SLMの例
      • Phi (Microsoft)
      • Llama (Meta)
      • Gemma (Google)
      • DeepSeek (DeepSeek)
      • Qwen (Alibaba)
  • .NET 9の標準的なDI(Dependency Injection)機能を提供する Microsoft.Extensions.DependencyInjection を利用しています。
  • DIで環境に応じてLLMやモデルなどを切り替えることができます。
    今回の例では、DOTNET_ENVIRONMENTDevelopment に設定されているため、Google Gemini API (Gemini 2.0 Flash) を呼び出しています。
  • Google Gemini APIへのアクセスは、Google Gemini APIのOpenAI API互換機能を使っています。
    OpenAI向けのライブラリのEndpointを変更することで、Google Gemini APIをOpenAI APIのように使えます。
  • 出力はストリーミングレスポンスにしているので、LLMから生成された文字列が少しずつ表示されていきます。

実行結果

AI(エーアイ)とは、Artificial Intelligence(アーティフィシャル・インテリジェンス)の略で、日本語では「人工知能」と訳さ れます。

**AIの定義**

AIには様々な定義がありますが、一般的には以下のように理解されています。

*   **人間の知的な活動を模倣・代替する技術**
*   **学習、推論、問題解決などの知的プロセスをコンピュータ上で実現する技術**

~~~ 長いので途中省略しました。 ~~~

より詳しく知りたい場合は、以下のキーワードで検索してみてください。

*   人工知能
*   機械学習
*   ディープラーニング
*   AI倫理

ご不明な点があれば、お気軽にご質問ください。
  • 今回の主題とは関係ないですが、Gemini 2.0 FlashとGPT-4o Miniは共に軽量モデル(価格も同程度)ですが、Gemini 2.0 Flashの方が後発の分、賢いです。

Promptyを使う

Promptyを使い、システムプロンプトを外部ファイルにします。

コード

Program.cs

using System.ClientModel;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenAI;
using Prompty.Core;

InvokerFactory.AutoDiscovery();

var app = new HostBuilder().Build();

var prompty = await Prompty.Core.Prompty.LoadAsync("./TranslateToJapanese.prompty");
var messages = (ChatMessage[])await prompty.PrepareAsync(new
{
    text = "What is AI?"
});

var client = app.Services.GetRequiredService<IChatClient>();
Console.WriteLine(await client.GetResponseAsync(messages));

internal sealed class HostBuilder
{
    public IHost Build()
    {
        var config =
            new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddUserSecrets<Program>()
                .Build();
        var builder = Host.CreateApplicationBuilder();
        builder.Services.AddChatClient(CreateGeminiChatClient(config));

        return builder.Build();
    }

    private IChatClient CreateGeminiChatClient(IConfigurationRoot config)
    {
        return
            new OpenAIClient(
                    new ApiKeyCredential(config["Gemini:ApiKey"]!),
                    new OpenAIClientOptions()
                    {
                        Endpoint = new Uri(config["Gemini:Endpoint"]!),
                    })
                .AsChatClient(config["Gemini:Model"]!);
    }
}

TranslateToJapanese.prompty

---
name: Translate To Japanese Prompt
model:
  api: chat
sample:
  text: 日本で一番高い山は何ですか?
---
system:
貴方は英語から日本語への翻訳者です。入力された英語の文章を日本語に翻訳します。

user:
{{text}}
  • Promptyは、Microsoftが提案しているLLMプロンプト開発を効率化し、再利用性や管理性を高めるための開発者向けツールとフォーマットです。仕様、ツール、ランタイムの3つの要素で構成され、オープンソースで開発が進められています。
    prompty.ai
    • 仕様
      • YAML形式のテキストファイルです。テキストファイルであるため、バージョン管理や共有が容易になります。
      • Promptyファイル中にLLMを動かすために必要な項目を記載することができます。Promptyファイルに記載することで、各プログラミング言語からLLMに関する情報を分離することができます。
        • 記載することが可能な項目例
          • LLMモデル名、LLMエンドポイント、システムプロンプト、ユーザプロンプト
    • ツール
      • Visual Studio Code上で動く拡張機能が提供されており、そのツール上で、システムプロンプトの作成、編集、動作確認をすることができます。
        Prompty - Visual Studio Marketplace
      • Azure AI FoundryなどからPromptyファイルの出力も可能です。
    • ランタイム
      • Promptyファイルから、各プログラミング言語やフレームワークで使用できる形に変換し、LLMを実行できます。
      • この機能は、Pythonで利用可能です。C#は開発中です。
  • 今回はシステムプロンプトを、LLMの機能は英語を日本語に翻訳することと定義しているため、LLMは翻訳のみを行います。
  • PromptyファイルTranslateToJapanese.promptyは出力フォルダにコピーする必要があるので、プロパティCopyToOutputDirectoryの値を、AlwaysまたはPreserveNewestにする必要があります。

実行結果

AI(人工知能)とは何か?
  • システムプロンプトとして、翻訳をするように定義しているため、そのように動作しています。
    (通常は入力された内容を解釈して動きますが、そのような挙動はしません。)

JSON出力をする

プログラムから利用しやすくする目的で、JSON出力をします。

コード

Program.cs

using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenAI;
using Prompty.Core;
using System.ClientModel;

var contents = new[]
{
    @"
## 問い合わせ内容

インターネットが接続できない。

## サポート内容

ルータの電源が入っているかを確認していただいた所、コンセントが抜けていることが判明しました。
コンセントを差し込んで接続されたことを確認した。
",
    @"
## 問い合わせ内容

昨年(2024年)の12月から接続ができていない。
今月の請求はどうなるのか?

## サポート内容

2024年12月の料金が発生することを説明。
接続障害は発生していないことを説明。
wifiルータが起動しないとのことで、購入の検討をお願いしました。
"
};

InvokerFactory.AutoDiscovery();

var app = new HostBuilder().Build();
var prompty = await Prompty.Core.Prompty.LoadAsync("./DescriptionClassifier.prompty");
var client = app.Services.GetRequiredService<IChatClient>();

foreach (var content in contents)
{
    var messages = (ChatMessage[])await prompty.PrepareAsync(new
    {
        text = content,
    });
    var chatCompletion = await client.GetResponseAsync(messages, new ChatOptions()
    {
        ResponseFormat = ChatResponseFormat.Json,
    });
    Console.WriteLine(chatCompletion.Message);
}
internal sealed class HostBuilder
{
    public IHost Build()
    {
        var config =
            new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddUserSecrets<Program>()
                .Build();
        var builder = Host.CreateApplicationBuilder();
        builder.Services.AddChatClient(CreateGeminiChatClient(config));

        return builder.Build();
    }

    private IChatClient CreateGeminiChatClient(IConfigurationRoot config)
    {
        return
            new OpenAIClient(
                    new ApiKeyCredential(config["Gemini:ApiKey"]!),
                    new OpenAIClientOptions()
                    {
                        Endpoint = new Uri(config["Gemini:Endpoint"]!),
                    })
                .AsChatClient(config["Gemini:Model"]!);
    }
}
  • LLMの呼び出し時、ResponseFormatをChatResponseFormat.Jsonと指定します。

DescriptionClassifier.prompty

---
name: Description Classifier
model:
  api: chat
sample:
  text: 接続ができない?
---
system:
貴方は優秀なインターネットサービス業務におけるサポート内容の分類器です。
文章からどのサポート内容に分類されるかを判定してください。

## 業務の種類

- 接続のサポート
- メーラーの設定
- 支払い

## output
{
  "category": 
  [
    "支払い"
  ]
}

user:
{{text}}
  • "Promptyを使う"の項と同様にDescriptionClassifier.promptyは、出力フォルダにコピーが必要です。

実行結果

{
  "category": [
    "接続のサポート"
  ]
}
{
  "category": [
    "接続のサポート",
    "支払い"
  ]
}
  • LLMの実行結果がJSONで返ってきます。
  • 2件のサポート内容を処理したので、結果も2件になっています。
  • 複数種類のサポート内容であっても正しく判断しています。

まとめ

  • C#でLLMを気軽に利用する環境が整ってきました。
  • DIと連携することで、環境などによって使うLLMを入れ替えることが可能です。
    • 例えば、運用環境ではOpenAI API、ローカル環境ではLlamaを使うなど。
  • 今回は扱っていませんが、Function callingや、Structured Outputsを使うことで、より高度な.NETとLLMの融合が可能です。

参考資料