Semantic Kernelを使ったローカルLLMチャット
MicrosoftがSemantic Kernelというものを使って簡単にAIプログラムを作成できるようにしてくださっているようです。もしかしたら、このやり方が今後のメインになるかも知れませんが、まだ仕様が動いておりファンクションやプロパティが変更になる可能性が高いとのことです。
Semantic Kernel について
Semantic Kernel は、OpenAI、Azure OpenAI、Hugging Face などの AI モデルとサービスを、C#、Python、Java などの従来のプログラミング言語と統合して調整するオープンソース SDK です。
Semantic Kernel SDK は、エンタープライズ開発者にとって次の点で役に立ちます。
- 既存のアプリケーションへの AI 機能の統合を合理化し、エンタープライズ製品のまとまりのあるソリューションを実現します。
- 複雑さを軽減する抽象化を提供することで、さまざまな AI モデルまたはサービスを操作する学習曲線を最小限に抑えます。
- AI モデルからの予期しないプロンプトと応答動作を減らすことで、信頼性が向上します。 プロンプトを最適化し、タスクを計画して、制御された予測可能なユーザー エクスペリエンスを作成できます。
Semantic Kernel は、いくつかのコア概念を中心に構築されています。
- 接続: 外部 AI サービス、データ ソースとのインターフェイス。
- プラグイン: アプリケーションで使用できるカプセル化された関数。
- プランナー: ユーザーの動作に基づく、プランと戦略実行の調整。
- メモリ: AI アプリのコンテキスト管理の抽象化および簡素化。
出典:NET 向け Semantic Kernel の概要 - Microsoft Learn
※SemanticKernel評価目的のエラーが出て前へ進めない場合はProjectを開いて<PropertyGroup></PropertyGroup>の間に下記を挿入してください。
<NoWarn>$(NoWarn);SKEXP0001,SKEXP0003,SKEXP0020,SKEXP0050,SKEXP0052</NoWarn>
なにやらよさげなので、ChatプログラムをSemantic Kernelに書き換えてみました。
Nuget情報
概要:簡単なチャットプログラムです。LLMはShadows-MoEを使用しています。チャット履歴はSqliteのデータベースを使用しています。
確かにコーディング量は減りましたね。パラメータもなくすっきりしています。
Program.cs
using LLama.Common;
using LLamaSharp.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.ChatCompletion;
namespace LLama.ChatProgram
{
public class Program
{
static void Main(string[] args)
{
//コンソールアプリケーションからAsyncを呼び出す
Task task = MainAsync();
//終了を待つ
task.Wait();
}
public static async Task MainAsync()
{
string strModelPath = Environment.GetEnvironmentVariable("LLMPATH", System.EnvironmentVariableTarget.User) + @"mradermacher\Shadows-MoE-GGUF\Shadows-MoE.Q8_0.gguf"; //◎
string strChatlogPath = Environment.GetEnvironmentVariable("CHATDB", System.EnvironmentVariableTarget.User) + @"ChatDB.db";
ChatHistoryDB chtDB;
bool Op = true;
Console.ForegroundColor = ConsoleColor.Blue;
var parameters = new ModelParams(strModelPath);
using var modPara = LLamaWeights.LoadFromFile(parameters);
var ex = new StatelessExecutor(modPara, parameters);
var llmChat = new LLamaSharpChatCompletion(ex);
var chtHis = llmChat.CreateNewChat();
chtDB = new ChatHistoryDB(strChatlogPath, chtHis);
// ユーザーからの質問
Console.ForegroundColor = ConsoleColor.White;
Console.Write("\nUser: ");
string strUserInput = "";
strUserInput = Console.ReadLine() ?? string.Empty;
while (strUserInput != "exit") //チャットはexitで終了します
{
chtHis.AddUserMessage(strUserInput);
if (Op) chtDB.WriteHistory(Microsoft.SemanticKernel.ChatCompletion.AuthorRole.User, strUserInput);
// 回答の表示
var reply = await llmChat.GetChatMessageContentAsync(chtHis);
chtHis.AddAssistantMessage(reply.Content);
string strMsg = MessageOutputAsync(chtHis);
if (Op) chtDB.WriteHistory(Microsoft.SemanticKernel.ChatCompletion.AuthorRole.User, strMsg);
CevioAI(strMsg.Replace("User:", "").Trim());
// ユーザーからの質問
Console.ForegroundColor = ConsoleColor.White;
Console.Write("\nUser: ");
strUserInput = Console.ReadLine() ?? string.Empty;
}
}
private static string MessageOutputAsync(Microsoft.SemanticKernel.ChatCompletion.ChatHistory chtHis)
{
Microsoft.SemanticKernel.ChatMessageContent message = chtHis.Last();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"{message.Role}: {message.Content.Replace("User:","").Trim()}");
return message.Content;
}
ChatHistoryDBは、Semantic Kernel用に変数の型を変更しています。
ChatHistoryDB_SC.cs
using Microsoft.SemanticKernel.ChatCompletion;
using System.Data.SQLite;
namespace LLama.ChatProgram
{
class ChatHistoryDB
{
Microsoft.SemanticKernel.ChatCompletion.ChatHistory? chtHis;
string strDbpath;
Dictionary<string, Microsoft.SemanticKernel.ChatCompletion.AuthorRole>? Roles = new Dictionary<string, Microsoft.SemanticKernel.ChatCompletion.AuthorRole> { { "System" , Microsoft.SemanticKernel.ChatCompletion.AuthorRole.System}, { "User", Microsoft.SemanticKernel.ChatCompletion.AuthorRole.User }, { "Assistant", Microsoft.SemanticKernel.ChatCompletion.AuthorRole.Assistant } };
public ChatHistoryDB(string strDbpath, Microsoft.SemanticKernel.ChatCompletion.ChatHistory chtHis)
{
this.strDbpath= strDbpath;
this.chtHis = chtHis;
try
{
var conSb = new SQLiteConnectionStringBuilder { DataSource = strDbpath };
var con = new SQLiteConnection(conSb.ToString());
con.Open();
using (var cmd = new SQLiteCommand(con))
{
cmd.CommandText="CREATE TABLE IF NOT EXISTS ch(" +
"sq INTEGER PRIMARY KEY," +
"dt TEXT NOT NULL," +
"id TEXT NOT NULL," +
"msg TEXT)";
cmd.ExecuteNonQuery();
cmd.CommandText="select * from ch order by sq";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
if (chtHis is null) chtHis=new ChatHistory();
chtHis.AddMessage(Roles[(string)reader["id"]], (string)reader["msg"]);
}
}
}
con.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public interface IDisposable
{
void Dispose();
}
public void WriteHistory(AuthorRole aurID, string strMsg,bool booHis=false)
{
try
{
var conSb = new SQLiteConnectionStringBuilder { DataSource = strDbpath };
var con = new SQLiteConnection(conSb.ToString());
con.Open();
using (var cmd = new SQLiteCommand(con))
{
if (booHis)
{
if (chtHis is null) chtHis=new ChatHistory();
chtHis.AddMessage(aurID, strMsg);
}
cmd.CommandText = $"insert into ch(dt,id,msg) values(datetime('now', 'localtime'),'{Roles.FirstOrDefault(v => v.Value.Equals(aurID)).Key}','{strMsg}')";
cmd.ExecuteNonQuery();
}
con.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
結果
実行してみました。パラメータは何も指定しない方がよさげです。勝手に内部で設定してくれてるようです。
AIに投げたときに表示がごちゃごちゃしていてAIからの返事が見えにくいのが難点です。これはコンソールには不向きかも知れません。Discordがフロントなら問題ないと思いますので次回はDiscord Bot用に書き換えてみます^^;