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();
            string page = await webClient.GetStringAsync("https://tenki.jp/");
 
            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] &lt; (long)HistoryMax || (long)reader[0] - (long)HistoryMax &lt; (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" &amp;&amp; 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&lt;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&lt;>'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名"

Follow me!

コメントを残す

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