Discord Botを使用しローカルLLMと会話するc#プログラム
前回の簡単なDiscord BotにLLMと会話するロジックを組み込みます。
今回からチャットログをSQLiteのデータベースに書き込む仕組みにしています。センスのいいインターフェイスではないので改造していただいて構いません^^;
下記をNugetしてください。LLamaSharp.BackendはCudaが前提になっていますのでCPUの方はNugetをそれ用にしてください。
SQLiteのデータベースを使ったチャットログClass
ChatHistoryDB.cs
using LLama.Common;
using System.Data.SQLite;
namespace DSharpPlus_Bots
{
class ChatHistoryDB
{
ChatHistory chtHis;
string strDbpath;
public ChatHistoryDB(string strDbpath, ChatHistory chtHis){
this.chtHis= chtHis;
this.strDbpath= strDbpath;
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())
{
AuthorRole aurID = AuthorRole.Unknown;
switch (reader["id"])
{
case "System":
aurID = AuthorRole.System; break;
case "User":
aurID = AuthorRole.User; break;
case "Assistant":
aurID = AuthorRole.Assistant; break;
}
if (chtHis is null) chtHis=new ChatHistory();
if (reader["msg"] != null) chtHis.AddMessage(aurID, (string)reader["msg"]);
}
}
}
con.Close();
}
public interface IDisposable
{
void Dispose();
}
public void WriteHistory(AuthorRole aurID, string strMsg,bool booHis = true)
{
var conSb = new SQLiteConnectionStringBuilder { DataSource = strDbpath };
var con = new SQLiteConnection(conSb.ToString());
con.Open();
using (var cmd = new SQLiteCommand(con))
{
string strId= "Unknown";
switch(aurID)
{
case AuthorRole.System:
strId="System"; break;
case AuthorRole.User:
strId="User"; break;
case AuthorRole.Assistant:
strId="Assistant"; break;
}
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'),'{strId}','{strMsg}')";
cmd.ExecuteNonQuery();
}
con.Close ();
}
}
}
SQLiteの保守を行うツールとしてDB Browserが便利です。下記からダウンロードできます。
窓の杜 DB Browser => https://forest.watch.impress.co.jp/library/software/sqldbbrowser/
スクリーンショットはこんな感じ。データの中身を見たり修正、削除もできます。
メインのロジックです
Program.cs
LLMの場所やSQLiteのデータベースの場所は、Environment.GetEnvironmentVariableでユーザー環境変数から取ってくる仕様にしています。
LLMにLlama3の「aya-23-8B」を使用していますので AntiPromprに"<|eot_id|>"を追加しています。
using DSharpPlus;
using DSharpPlus.Entities;
using LLama.Common;
using LLama;
namespace DSharpPlus_Bots
{
public sealed class Program
{
private static ChatSession? chtSess;
private static InferenceParams? infPara;
// LLMモデルの場所
private static string strModelPath = Environment.GetEnvironmentVariable("LLMPATH", System.EnvironmentVariableTarget.User) + @"bartowski\aya-23-8B-GGUF\aya-23-8B-Q8_0.gguf";
private static string strChatlogPath = Environment.GetEnvironmentVariable("CHATDB", System.EnvironmentVariableTarget.User) + @"ChatDB.db";
private static ChatHistoryDB? chtDB;
public static async Task Main()
{
Console.ForegroundColor = ConsoleColor.Blue;
//LLMモデルのロードとパラメータの設定
ModelParams modPara = new(strModelPath)
{
ContextSize = 512,
Seed = 1337,
GpuLayerCount = 24,
};
using LLamaWeights llmWeit = LLamaWeights.LoadFromFile(modPara);
using LLamaContext llmContx = llmWeit.CreateContext(modPara);
InteractiveExecutor itrEx = new(llmContx);
//チャットログを読み込みます。これがないと一からの会話になります。ログが存在しない場合はキャラ設定のみにします
ChatHistory chtHis = new ChatHistory();
if (File.Exists(strChatlogPath))
{
chtDB = new ChatHistoryDB(strChatlogPath, chtHis);
}
else
{
chtDB = new ChatHistoryDB(strChatlogPath, chtHis);
chtDB.WriteHistory(AuthorRole.System, "アシスタントは、マイという名前の女の子です。マイは、明るくて、やさしい性格です。年齢は24歳です。職業は事務系のOLです。外見の特徴として体型は胸とお尻が大きく身長は165cmです。恥ずかしがりやですがエッチには興味があります。");
}
chtSess = new(itrEx, chtHis);
var varHidewd = new LLamaTransforms.KeywordTextOutputStreamTransform(["User:", "Assistant: "]);
chtSess.WithOutputTransform(varHidewd);
infPara = new()
{
Temperature = 0.6f,
AntiPrompts = ["User:", "<|eot_id|>"],
MaxTokens = 256,
};
//ここからDiscordの処理
// ユーザー環境変数に登録した「DISCORD_TOKEN」を持ってくる
string? token = Environment.GetEnvironmentVariable("DISCORD_TOKEN", System.EnvironmentVariableTarget.User);
if (string.IsNullOrWhiteSpace(token))
{
Console.WriteLine("Please specify a token in the DISCORD_TOKEN environment variable.");
Environment.Exit(1);
return;
}
DiscordConfiguration config = new()
{
Token = token,
Intents = DiscordIntents.AllUnprivileged | DiscordIntents.MessageContents
};
DiscordClient client = new(config);
//メッセージイベント
client.MessageCreated += async (client, eventArgs) =>
{
//ボットに応答しない
if(eventArgs.Message.Author.Id==client.CurrentUser.Id)
return;
// ユーザーのターン
ChatHistory.Message msg = new(AuthorRole.User, "question: " + eventArgs.Message.Content);
chtDB.WriteHistory(AuthorRole.User, "question: " + eventArgs.Message.Content, false);
// AIのターン
string strMsg = "";
await foreach (string strAns in chtSess.ChatAsync(msg, infPara))
{
strMsg += strAns;
}
await eventArgs.Message.RespondAsync(strMsg);
chtDB.WriteHistory(AuthorRole.Assistant, strMsg, false);
};
DiscordActivity status = new("with fire", ActivityType.Playing);
await client.ConnectAsync(status, UserStatus.Online);
await Task.Delay(-1);
}
}
}
Discordを使ってマイ嬢との会話が下記です