AIサーバー試作品1.5(音声会話のみ)
前回に続きAIサーバー試作品のDiscordがないバージョンです。試作品なのでバグが多くてちょこちょこ直しています。自分の環境はGeforce RTX3090 24GBなのですがGpuLayerCountを24から60に変更して爆速になりました。GpuLayerCountがメモリ数ではないことは、以前からわかってたんですが・・・
Discord Botは、これが安定したら組み込む予定。
1.4からの修正点
・App.Settingにtmpを追加

Nuget情報
1.4からの修正点
・HtmlAgilityPackを追加

概要
音声会話するだけのAIプログラム。5分話さないと答えなくなります。Awakeキーワードに指定された”まい”とか”まいちゃん”とか呼ばれると会話が再開できます。
逆に「さよなら」や「おやすみ」というと寝ます。「ばいばい」はVOSKが時々「売買」と解釈するので省きました。
20秒間話しかけないとAIの方から質問してきます。機嫌によって話しかけない場合もあります。
App.Settingで音声ソフトの設定をしてください。対象は、CeVIOAIとVOICEVOXです。VOICEVOXの場合、プロセスにないと起動させるコマンドを追加していますが、どこにインストールされているのか探すコードを書くのがめんどくさかったのでApp.Settingにしています。自分の環境に合わせて修正してください。あと自分の環境はデバッグと本番のディレクトリ構成が違うため環境変数やらいっぱい使ってるので直書きかApp.Settingに直してください。
AIから話しかけるようにシステム行(sq=1)に「??」をトリガー指定しています。
1 2 3 4 5 6 7 8 9 | あなたは、ユーザーと楽しく会話する女性を演じてね。 名前は、マイだよ。 マイは、明るくて、やさしい性格の女性なの。年齢は24歳なんだよ。 マイは、誰にでもタメ口で話すの。 マイは、一人称をわたしと言うよ。 マイは、恥ずかしがりやだけどエッチに興味があるのね。 マイは、応答の長さはできるだけ短く答えるよ。 マイは、好奇心旺盛だからユーザーに色々質問するの。 ユーザーが??と問いかけたら、マイはユーザーに質問をするんだよ。 |
Program.cs メイン
1.4からの修正点
・App.SettingのtmpからTemperatureを設定
・AIから話しかけてくるところにWtフラグを追加
・GpuLayerCountを24から60に変更
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | using LLama.Common; using LLama; using LLama.Sampling; using System.Media; using System.Net.Http.Headers; using System.Diagnostics; using System.Speech.Recognition; using System.Text.Json; using Vosk; namespace AI_Chat { public class Program { static void Main( string [] args) { Task task = MainAsync(); task.Wait(); } private static async Task MainAsync() { bool Op = true ; //本番フラグ bool msg = false ; //メッセージ表示 uint intContextSize = 4096; //コンテクスト長さ uint silenceTimerCnt = 0; //寝るTimerカウント // LLMモデルの場所 string strModelPath = Environment.GetEnvironmentVariable( "LLMPATH" , System.EnvironmentVariableTarget.User) + @"dahara1\gemma-2-27b-it-gguf-japanese-imatrix\gemma-2-27b-it.f16.Q5_k_m.gguf" ; //Vosk設定 Vosk.Vosk.SetLogLevel(-1); //LogメッセージOFF Model model = new Model(Environment.GetEnvironmentVariable( "LLMPATH" , System.EnvironmentVariableTarget.User)+ @"vosk\vosk-model-ja-0.22" ); try { //チャットデータベース string strChatlogPath = Environment.GetEnvironmentVariable( "CHATDB" , System.EnvironmentVariableTarget.User) + @"ChatDB.db" ; string strTable = App.Default.ChatDB_Table; //Awake、Asleep設定 const uint itrvl = 10000; // 10秒 const uint silenceTimerMax = 30; //寝待ち長さ string [] AwakeWord = App.Default.AwakeKeyWord.Split( "," ); string [] AsleepWord = App.Default.AsleepKeyWord.Split( "," ); System.Timers.Timer? silenceTimer; //チャットログ要約処理 Task task = HistorySummary.Run(strChatlogPath, intContextSize / 2, strTable); task.Wait(); //チャットログシステム ChatHistoryDB chtDB; //AI待ちフラグ bool Wt = false ; //AIスリープフラグ bool Slp = false ; //LLMモデルのロードとパラメータの設定 Console.ForegroundColor = ConsoleColor.Blue; var modPara = new ModelParams(strModelPath) { ContextSize = intContextSize, Seed = 1337, GpuLayerCount = 60, }; LLamaWeights llmWeit = LLamaWeights.LoadFromFile(modPara); LLamaContext llmContx = llmWeit.CreateContext(modPara); InteractiveExecutor itrEx = new (llmContx); //チャットログを読み込みます。 ChatHistory chtHis = new ChatHistory(); chtDB = new ChatHistoryDB(strChatlogPath, chtHis, strTable); ChatSession chtSess = new (itrEx, chtHis); var varHidewd = new LLamaTransforms.KeywordTextOutputStreamTransform([ "User: " , "Assistant: " ]); chtSess.WithOutputTransform(varHidewd); InferenceParams infPara = new () { SamplingPipeline = new DefaultSamplingPipeline() { Temperature = App.Default.tmp, }, AntiPrompts = [ "User:" ], MaxTokens = 256, }; //タイマーとイベント定義 silenceTimer = new System.Timers.Timer(itrvl); silenceTimer.Elapsed += async (sender, e) => { silenceTimerCnt++; //AIから話しかける仕組み if (silenceTimerCnt == 2) { Random rTalk = new Random(); if (rTalk.Next(0, 1) == 0) //乱数で0が出たら話しかける。トリガーは"??" { if (!Wt) { Wt = true ; string strUserInput = "??" ; ChatHistory.Message msgText = new (AuthorRole.User, strUserInput); if (Op) chtDB.WriteHistory(AuthorRole.User, "User: " + strUserInput); // 回答の表示 Console.ForegroundColor = ConsoleColor.Yellow; string strMsg = "" ; await foreach ( string strText in chtSess.ChatAsync(msgText, infPara)) strMsg += strText; //発信するときは「User:」や「Assistant:」を抜く string strSndmsg = strMsg.Replace( "User:" , "" ).Replace( "Assistant:" , "" ).Replace( "assistant:" , "" ).Trim(); if (msg) Console.WriteLine( "Assistant: " + strSndmsg); if (Op) chtDB.WriteHistory(AuthorRole.Assistant, strMsg); //音声ソフト実行 SpeechSynthesis(strSndmsg); Wt = false ; } } } if (silenceTimerCnt >= silenceTimerMax) { Wt = true ; Slp = true ; if (msg) Console.WriteLine( "休止しました。" ); silenceTimerCnt = 0; silenceTimer.Stop(); } }; // SpeechRecognitionの設定 //Awake設定とイベント定義 SpeechRecognitionEngine recognizer2 = new SpeechRecognitionEngine( new System.Globalization.CultureInfo( "ja-JP" )); Choices awake = new Choices(AwakeWord); GrammarBuilder gb2 = new GrammarBuilder(awake); recognizer2.LoadGrammar( new Grammar(gb2)); recognizer2.SpeechRecognized += (sender, e) => { if (e.Result.Confidence > 0.8 && Slp) { if (msg) Console.WriteLine($ "{e.Result.Text}と呼ばれました!" ); Slp = false ; Wt = false ; silenceTimerCnt = 0; silenceTimer.Stop(); silenceTimer.Start(); } }; //Asleep設定とイベント定義 SpeechRecognitionEngine recognizer3 = new SpeechRecognitionEngine( new System.Globalization.CultureInfo( "ja-JP" )); Choices asleep = new Choices(AsleepWord); GrammarBuilder gb3 = new GrammarBuilder(asleep); recognizer3.LoadGrammar( new Grammar(gb3)); recognizer3.SpeechRecognized += (sender, e) => { if (e.Result.Confidence > 0.8) { if (msg) Console.WriteLine($ "{e.Result.Text}と言われました" ); Slp = true ; } }; //AI会話設定とイベント定義 SpeechRecognitionEngine recognizer = new SpeechRecognitionEngine( new System.Globalization.CultureInfo( "ja-JP" )); recognizer.LoadGrammar( new DictationGrammar()); // ▼▼▼ ここからSpeechRecognizedイベント定義 開始 ▼▼▼ recognizer.SpeechRecognized += async (sender, e) => { if (!Wt) { Wt = true ; silenceTimerCnt = 0; silenceTimer.Stop(); //RecognizedしたwaveをMemoryStreamに書き込み MemoryStream st = new MemoryStream(); e.Result.Audio.WriteToWaveStream(st); st.Position = 0; // byte buffer VoskRecognizer rec = new VoskRecognizer(model, 16000.0f); rec.SetMaxAlternatives(0); byte [] buffer = new byte [4096]; int bytesRead; while ((bytesRead = st.Read(buffer, 0, buffer.Length)) > 0) rec.AcceptWaveform(buffer, bytesRead); string strUserInput = "" ; // Json形式のResultからテキストを抽出 string jsontext = rec.FinalResult(); var jsondoc = JsonDocument.Parse(jsontext); if (jsondoc.RootElement.TryGetProperty( "text" , out var element)) strUserInput = element.GetString() ?? "" ; strUserInput = strUserInput.Replace( " " , "" ); Console.ForegroundColor = ConsoleColor.White; if (msg) Console.WriteLine($ "User: {strUserInput}" ); ChatHistory.Message msgText = new (AuthorRole.User, "User: " + strUserInput); if (Op) chtDB.WriteHistory(AuthorRole.User, "User: " + strUserInput); // 回答の表示 Console.ForegroundColor = ConsoleColor.Yellow; string strMsg = "" ; await foreach ( string strText in chtSess.ChatAsync(msgText, infPara)) strMsg += strText; //発信するときは「User:」や「Assistant:」を抜く string strSndmsg = strMsg.Replace( "User:" , "" ).Replace( "Assistant:" , "" ).Replace( "assistant:" , "" ).Trim(); if (msg) Console.WriteLine( "Assistant: " + strSndmsg); if (Op) chtDB.WriteHistory(AuthorRole.Assistant, strMsg); //音声ソフト実行 SpeechSynthesis(strSndmsg); //MemoryStream破棄 st.Close(); st.Dispose(); //Wt解除、Timer Restart(寝に入っていたら解除しない) if (!Slp) Wt = false ; if (!Slp) silenceTimer.Start(); } }; // ▲▲▲ ここからSpeechRecognizedイベント定義 終了 ▲▲▲ // Awake - Configure input to the speech recognizer. recognizer2.SetInputToDefaultAudioDevice(); // Awake - Start asynchronous, continuous speech recognition. recognizer2.RecognizeAsync(RecognizeMode.Multiple); // Asleep - Configure input to the speech recognizer. recognizer3.SetInputToDefaultAudioDevice(); // Asleep - Start asynchronous, continuous speech recognition. recognizer3.RecognizeAsync(RecognizeMode.Multiple); // AI会話 - Configure input to the speech recognizer. recognizer.SetInputToDefaultAudioDevice(); // AI会話 - Start asynchronous, continuous speech recognition. recognizer.RecognizeAsync(RecognizeMode.Multiple); // Timer Start silenceTimer.Start(); Console.WriteLine( "★★ マイクに向かって話してください ★★" ); // Keep the console window open. while ( true ) { Console.ReadLine(); } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } private static void SpeechSynthesis( string strMsg) { //音声ソフト選択 if (App.Default.SpeechSynth==1) CevioAI(strMsg); if (App.Default.SpeechSynth==2) { if (Process.GetProcessesByName( "VOICEVOX" ).Length == 0) { var vx = new ProcessStartInfo(); vx.FileName = Environment.GetEnvironmentVariable( "VOICEVOX_PATH" , System.EnvironmentVariableTarget.User); vx.UseShellExecute = true ; Process.Start(vx); Thread.Sleep(2000); } Task task = Voicevox(strMsg); task.Wait(); } } private static void CevioAI( string strMsg) { try { dynamic service = Activator.CreateInstance(Type.GetTypeFromProgID( "CeVIO.Talk.RemoteService2.ServiceControl2V40" )); service.StartHost( false ); dynamic talker = Activator.CreateInstance(Type.GetTypeFromProgID( "CeVIO.Talk.RemoteService2.Talker2V40" )); talker.Cast = App.Default.CEVIOAI_CAST; dynamic result = talker.Speak(strMsg); result.Wait(); //開放忘れるとメモリリーク System.Runtime.InteropServices.Marshal.ReleaseComObject(talker); System.Runtime.InteropServices.Marshal.ReleaseComObject(service); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } private static async Task Voicevox( string strMsg) { MemoryStream? ms; try { using ( var httpClient = new HttpClient()) { string strQuery; // 音声クエリを生成 using ( var varRequest = new HttpRequestMessage( new HttpMethod( "POST" ), $ "http://127.0.0.1:{App.Default.VOICEVOX_PORT}/audio_query?text={strMsg}&speaker={App.Default.VOICEVOX_SPEAKER}&speedScale=1.5&prePhonemeLength=0&postPhonemeLength=0&intonationScale=1.16&enable_interrogative_upspeak=true" )) { varRequest.Headers.TryAddWithoutValidation( "accept" , "application/json" ); varRequest.Content = new StringContent( "" ); varRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse( "application/x-www-form-urlencoded" ); var response = await httpClient.SendAsync(varRequest); strQuery = response.Content.ReadAsStringAsync().Result; } // 音声クエリから音声合成 using ( var request = new HttpRequestMessage( new HttpMethod( "POST" ), $ "http://127.0.0.1:{App.Default.VOICEVOX_PORT}/synthesis?speaker={App.Default.VOICEVOX_SPEAKER}&enable_interrogative_upspeak=true&speedScale=1.5&prePhonemeLength=0&postPhonemeLength=0&intonationScale=1.16" )) { request.Headers.TryAddWithoutValidation( "accept" , "audio/wav" ); request.Content = new StringContent(strQuery); request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse( "application/json" ); var response = await httpClient.SendAsync(request); // 音声を保存 using (ms = new MemoryStream()) { using ( var httpStream = await response.Content.ReadAsStreamAsync()) { httpStream.CopyTo(ms); ms.Flush(); } } } } ms = new MemoryStream(ms.ToArray()); //読み込む var player = new SoundPlayer(ms); //再生する player.PlaySync(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } } } |
ChatHistoryDB.cs SQLiteでチャット履歴を管理
1.4からの修正点
・データベースのSq=3をSystem用に変更し要約が入るようにした
・データベースのSq=2は要約が入る仕様だったが、今日の日付、株価、天気の情報が入るように変更
・ホームページから株価、天気予報を持ってきて編集する関数を追加
・System行からChatHistoryに書き出す時は1行にするように変更
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | using LLama.Common; using System.Data.SQLite; using HtmlAgilityPack; namespace AI_Chat { class ChatHistoryDB { ChatHistory? chtHis; string strDbpath; Dictionary< string , AuthorRole>? Roles = new Dictionary< string , AuthorRole> { { "System" , AuthorRole.System }, { "User" , AuthorRole.User }, { "Assistant" , AuthorRole.Assistant } }; string strTable; public ChatHistoryDB( string strDbpath, ChatHistory chtHis, string strTable) { this .chtHis= chtHis; this .strDbpath= strDbpath; this .strTable= strTable; 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 {strTable}(" + "\"sq\" INTEGER," + "\"dt\" TEXT NOT NULL," + "\"id\" TEXT NOT NULL," + "\"msg\" TEXT," + "\"flg\" INTEGER DEFAULT 0, PRIMARY KEY(\"sq\"))" ; cmd.ExecuteNonQuery(); cmd.CommandText = $ "select count(*) from {strTable}" ; using ( var reader = cmd.ExecuteReader()) { //一行も存在しない場合はシステム行をセットアップ long reccount = 0; if (reader.Read()) reccount = ( long )reader[0]; reader.Close(); if (reccount<1) { //Assistantの性格セットアップ行追加 cmd.CommandText = $ "insert into {strTable}(dt,id,msg) values(datetime('now', 'localtime'),'System','あなたは優秀なアシスタントです。')" ; cmd.ExecuteNonQuery(); //要約行追加 cmd.CommandText = $ "insert into {strTable}(dt,id,msg) values(datetime('now', 'localtime'),'System','')" ; //最新情報行追加 cmd.CommandText = $ "insert into {strTable}(dt,id,msg) values(datetime('now', 'localtime'),'System','')" ; cmd.ExecuteNonQuery(); } } //最新情報を更新 cmd.CommandText = $ "update {strTable} set dt=datetime('now', 'localtime'),msg='今日は、{DateTime.Now.ToString(" yyyy年M月d日dddd ")}です。\n" + GetWether().Result + "\n" + GetNikkeiStkAvg().Result + "' where sq=3" ; cmd.ExecuteNonQuery(); //システム行を履歴に読み込む" string strMsg = "" ; cmd.CommandText = $ "select * from {strTable} where id='System' order by sq" ; using ( var reader = cmd.ExecuteReader()) { while (reader.Read()) { strMsg += ( string )reader[ "msg" ] + "\n" ; } } if (chtHis is null ) chtHis= new ChatHistory(); chtHis.AddMessage(AuthorRole.System,strMsg); //会話行を履歴に読み込む cmd.CommandText = $ "select * from {strTable} where flg=0 and id<>'System' order by sq" ; using ( var reader = cmd.ExecuteReader()) { while (reader.Read()) { 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 {strTable}(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); } } private static async Task< string > GetNikkeiStkAvg() { // 株価取得 HttpClient webClient = new HttpClient(); string page = await webClient.GetStringAsync( "https://www.nikkei.com/markets/worldidx/chart/nk225/" ); var htmlDocument = new HtmlDocument(); htmlDocument.LoadHtml(page); // Select nodes using XPath var node = htmlDocument.DocumentNode.SelectSingleNode( "//span[@class=\"economic_value_now a-fs26\"]" ); // Extract and display data string txtcontents = "日経平均株価は、不明です。" ; if (node is not null ) txtcontents = $ "日経平均株価は、{node.InnerText.Trim()}円です。" ; node = htmlDocument.DocumentNode.SelectSingleNode( "//span[@class=\"economic_value_time a-fs14\"]" ); if (node is not null ) txtcontents += node.InnerText.Trim(); //Console.WriteLine(txtcontents); return txtcontents; } private static async Task< string > GetWether() { // 天気予報 HttpClient webClient = new HttpClient(); var htmlDocument = new HtmlDocument(); htmlDocument.LoadHtml(page); // Select nodes using XPath var nodes = htmlDocument.DocumentNode.SelectNodes( "//div[@class=\"forecast-comment\"]" ); string txtcontents = "今日の天気は、不明です。" ; // Extract and display data if (nodes is not null ) { txtcontents = "今日の天気は、「" ; foreach ( var node in nodes) { txtcontents += node.InnerText.Trim(); } txtcontents += "」です。" ; } //Console.WriteLine(txtcontents); return txtcontents; } } } |
HistorySummary.cs 長くなった会話を要約して圧縮しSEQ=3のシステム行のmsgに追加します。要約が終わった行は論理削除。
要約には「DataPilot-ArrowPro-7B-RobinHood」が優秀だったので使ってます。
1.4からの修正点
・データベースのSq=3に要約が入る仕様に変更
・要約は追記型だったが文字が増えすぎるため要約行を含めて再要約する仕様にした
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | using LLama.Common; using LLama; using System.Data.SQLite; namespace AI_Chat { internal class HistorySummary { public static async Task Run(string strChatlogPath, uint HistoryMax, string strTable) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("**Start History Summary**"); try { // LLMモデルの場所 string strModelPath = Environment.GetEnvironmentVariable("LLMPATH", System.EnvironmentVariableTarget.User) + @"mmnga\DataPilot-ArrowPro-7B-RobinHood-gguf\DataPilot-ArrowPro-7B-RobinHood-Q8_0.gguf"; // ChatDBから行を読み込む string strChtHis = ""; uint i = 0; uint intCtxtSize = 4096; //一度に処理できる文字数 long svSq = 0; //処理を開始したSq long svSqMax = 0; //処理を終了したSq var conSb = new SQLiteConnectionStringBuilder { DataSource = strChatlogPath }; var con = new SQLiteConnection(conSb.ToString()); con.Open(); using (var cmd = new SQLiteCommand(con)) { //有効文字数をカウント cmd.CommandText = $"select sum(length(msg)) from {strTable} where flg=0"; using (var reader = cmd.ExecuteReader()) { reader.Read(); //Console.WriteLine($"文字数:{reader[0]}"); //チャット履歴の最大行に満たないか、最大文字数を越えても処理できる文字数が充分でない場合は処理をしない if ((long)reader[0] < (long)HistoryMax || (long)reader[0] - (long)HistoryMax < (long)intCtxtSize) { con.Close(); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("**E.N.D History Summary**"); return; } } //有効行を呼んで要約する cmd.CommandText = $"select * from {strTable} where flg=0 and sq>2 order by sq"; string strCht = ""; using (var reader = cmd.ExecuteReader()) { while (reader.Read()) { strCht = (string)reader["id"] + ": " + ((string)reader["msg"]).Replace("User:", "").Replace("Assistant:", "").Replace("assistant:", "").Trim() + "\n"; i += (uint)strCht.Length; if (i > intCtxtSize) { break; } strChtHis += strCht; //システム行以外で処理を開始した行を記録 if ((string)reader["id"] != "System" && svSq == 0) { svSq = (long)reader["sq"]; } svSqMax = (long)reader["sq"]; } } } //LLMの処理 Console.ForegroundColor = ConsoleColor.Blue; //LLMモデルのロードとパラメータの設定 ModelParams modPara = new(strModelPath) { ContextSize = intCtxtSize, GpuLayerCount = 60 }; ChatHistory chtHis = new ChatHistory(); chtHis.AddMessage(AuthorRole.System, "#命令書\n" + "・あなたは優秀な編集者です。\n" + "・ユーザーとアシスタントの会話を要約してください。\n" + "#条件\n" + "・重要なキーワードを取りこぼさない。\n" + "#出力形式\n" + "・例)要約: ふたりは親密な会話しました。"); using LLamaWeights llmWeit = LLamaWeights.LoadFromFile(modPara); using LLamaContext llmContx = llmWeit.CreateContext(modPara); InteractiveExecutor itrEx = new(llmContx); ChatSession chtSess = new(itrEx, chtHis); var varHidewd = new LLamaTransforms.KeywordTextOutputStreamTransform(["User:", "Assistant:"]); chtSess.WithOutputTransform(varHidewd); InferenceParams infPara = new() { Temperature = 0f, AntiPrompts = new List<string> { "User:" } }; // ユーザーのターン Console.ForegroundColor = ConsoleColor.White; string strInput = strChtHis; ChatHistory.Message msg = new(AuthorRole.User, strInput); // AIのターン Console.ForegroundColor = ConsoleColor.Magenta; string strMsg = ""; await foreach (string strAns in chtSess.ChatAsync(msg, infPara)) { Console.Write(strAns); strMsg += strAns; } using (var cmd = new SQLiteCommand(con)) { //sq=1に要約内容を更新する(要約という文字、改行、空白を削除) string strSql = $"update {strTable} set msg= '{strMsg.Replace("要約:", "").Replace("\r", "").Replace("\n", "").Trim()}' where sq=3"; //Console.Write (strSql+"\n"); cmd.CommandText = strSql; cmd.ExecuteNonQuery(); //処理した行を論理削除する strSql = $"update {strTable} set flg=1 where (sq between {svSq} and {svSqMax}) and id<>'System' and flg=0"; //Console.Write(strSql+"\n"); cmd.CommandText = strSql; cmd.ExecuteNonQuery(); } //DBクローズ con.Close(); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("**E.N.D History Summary**"); } } } |
ずっと立ち上げるならスケジューラで再起動すると要約が走るのでいいかも
PowerShellコマンドの参考例
AI_Server01.ps1
1 2 3 | stop-process -Name "アプリのプロセス名" Start-Sleep -Seconds 3 Start-Process -FilePath "アプリパス+EXE名" |