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を使ってマイ嬢との会話が下記です

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です