🧙‍♂️

Semantic Kernelを使ってC#でAI (1)

に公開

AIアプリの実装

AIと言えばPython。
AIで実装と言えばLangChain。そしてPython。
Python嫌いではないとはいえ、もうちょっと型と見える範囲に気を使ってくれ、と思わなくもない。

なんとかC#で実装できないか、と思っていたら、C#にもある。 LangChain
これでもいいんですが、もう少し探して。
ありました。 Semantic Kernel
こちらはMS様作。

今回も見事に 目的と手段が逆転 してます。

Semantic Kernel

Semantic Kernelは見た感じ、

  • 今までのC#の設計思想に寄り添う
  • できるだけ周辺の実装を取り込む方向で
  • できるだけ実装を仮想化しつつ
  • おしゃべりよりもエージェント方面

という雰囲気が漂います。

チャットサンプル

早速、AIとチャットできるプログラムを作ってみましょう。
コンソールからOllamaとチャットできるだけのプログラムです。

プロジェクト作成

プロジェクト作成
$ dotnet new console
$ dotnet add package Microsoft.SemanticKernel
$ dotnet add package Microsoft.SemanticKernel.Connectors.Ollama --prerelease
$ dotnet add package DotEnv.Core
.env
OLLAMA_MODEL=gemma3:12b
Program.cs
using DotEnv;
using DotEnv.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using System.Runtime.CompilerServices;

new EnvLoader().Load();

var builder = Kernel.CreateBuilder();

#pragma warning disable SKEXP0070 // 種類は、評価の目的でのみ提供されています。将来の更新で変更または削除されることがあります。続行するには、この診断を非表示にします。
builder.AddOllamaChatCompletion(Environment.GetEnvironmentVariable("OLLAMA_MODEL"), new Uri("http://localhost:11434"));
#pragma warning restore SKEXP0070 // 種類は、評価の目的でのみ提供されています。将来の更新で変更または削除されることがあります。続行するには、この診断を非表示にします。

var kernel = builder.Build();

var chatCompletion = kernel.Services.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();

string? line;
do
{
    Console.Write("User > ");
    line = Console.ReadLine();
    history.AddUserMessage(line);

    var assistant = await chatCompletion.GetChatMessageContentAsync(history);
    history.AddAssistantMessage(assistant.Content);

    Console.WriteLine($"assistant > {assistant.Content}");
    Console.WriteLine();
} while (!string.IsNullOrEmpty(line));

Semantic KernelのスタートアップにあるコードをOllamaに接続するようにしてチャットの部分だけ抜き出したものです。
これだけでもAIとのチャットができ、履歴も勘案されるものになってます。
LangChainを少しでもかじったことがあればすぐに理解できるでしょう。

プログラム解説

依存性の注入

C#のプログラムの組み方、特にASP.NetやらMAUIやらをやっている方はプログラムを見て、おやっ、と思ったかもしれません。

var builder = Kernel.CreateBuilder();
var kernel = builder.Build();
var chatCompletion = kernel.Services.GetRequiredService<IChatCompletionService>();

この流れはどこかで見たことありますよね?
そう、DependencyInjectionパターンです。
上記プログラム内でも using されてます。

モデルの選択

builder.AddOllamaChatCompletion(Environment.GetEnvironmentVariable("OLLAMA_MODEL"), new Uri("http://localhost:11434"));

Ollamaに接続するように設定している部分です。
ここを上記スタートアップページに書かれているように、

builder.AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);

と書き換えることでAzure OpenAIに接続してチャットができるようになります。
このあたりがDIパターンで生きてきますね。

実はこのように1行ごとに書かずにメソッドチェーンで

    var kernel = Kernel
        .CreateBuilder()
        .AddOllamaChatCompletion(Environment.GetEnvironmentVariable("OLLAMA_MODEL"), new Uri("http://localhost:11434"))
        .Build();

と書けるのですが、まだいろいろ未完成のようで、 SKEXP0070 とかエラーが出るので仕方なくこのように書いています。
(早く実践で使えるようなこのようなエラーを抑制したものにしてください…MSさん…)

入力と返答

入力を覚えておくのは下記になります。

var history = new ChatHistory();
history.AddUserMessage(line);
history.AddAssistantMessage(assistant.Content);

history作ってそこにuser、assistantに設定するあたりはLangChainやっている方にはおなじみでしょう。
これでAIとのやり取りを覚えてくれます。

実際の入力からAIの返答を作成するのは、以下になります。

var assistant = await chatCompletion.GetChatMessageContentAsync(history);

まとめ

いかがでしたでしょうか。
非常に簡単にAIとの応答をするプログラムを作ることができます。
ここから何度か使って実戦で使えるようなテクニックを書いてみましょう。

Discussion