diff --git a/README.md b/README.md index 0c6a045..66077bc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Fujitsu Cloud Service Genearative AI Platform の API を利用するための C --- # 使い方 -## 起動 +## 1. 起動 1. SampleCSharpUI.exe をクリックして起動 2. 【初回起動時のみ】 自動的にテナント名とクライアントIDを入力する [ 設定 ] 画面を表示します。 @@ -29,7 +29,7 @@ Fujitsu Cloud Service Genearative AI Platform の API を利用するための C | 非対話認証 | 秘密キーによる非対話が可能な契約時に選択 | | ClientSecret | 契約時に通知された秘密キーを設定 | -## 会話関連の操作 +## 2. 会話関連の操作 ### 会話入力 - 画面下の [ 入力 ] 欄に質問を入力し、 Enter を押すことで送信されます。 - 改行を入力したいときは、 Shift+Enter で改行ができます。 @@ -50,13 +50,13 @@ Generative AI Platform は、プロンプトパターンとして会話履歴プ そのため、ルームに過去の会話が残っていると、その履歴に影響を受けた回答となります。そのため、新しい会話を始めたいときは過去の会話履歴を削除する必要があります。 過去の会話履歴の削除は、 [ ルーム ]-[ 会話をクリア ] メニューをクリックすることで実現できます。 -## ルーム関連の操作 +## 2. ルーム関連の操作 ### ルームの選択 - メニューに表示されているルーム名をクリックすると作成済ルーム一覧がリスト表示されるので、そこから移動したいルームを選択してください。 ### ルームの新規作成 [ ルーム ]-[ 新規作成 ] メニューをクリックして、ルームの新規作成画面を表示します。 -1. [ ルーム名 ] 欄に識別名を入力し、利用するRAGを [ リトリーバー ] 選択から選択します。なお、RAG不要時は「 none 」を選択します。 +1. [ ルーム名 ] 欄に識別名を入力し、利用するRAGを [ RAG指定 ] 選択から選択します。なお、RAG不要時は「 none 」を選択します。 1. [OK] をクリックしルームを作成します。作成が成功したときは自動的に作成したルームに移動します。 ### ルームを管理 @@ -73,7 +73,42 @@ Generative AI Platform は、プロンプトパターンとして会話履歴プ [ ルーム管理 ] 画面で [ 削除 ] をクリックすると該当行のルームを削除できます。 - 削除前に確認ダイアログが表示されるので、誤操作でクリックしてしまった時は [ いいえ ] をクリックすると削除を中止できます。 -## RAG関連の操作 +## 4. ルームなし推論 + +ルーム不要でTakaneに対してプロンプトを入力し回答を得るだけでよい場合には、ルームの設定や管理などの事前実行が必要なWebAPI呼び出しは煩雑に感じてしまいます。 +そのようなユースケースに対するサンプルコードとして「ルームなし会話」モードを実装しています。 + +### ルームなしの選択 +ルームの選択で「(none)」を選択するとチャットルームなしでの利用となります。 +サンプルコード内で画面に表示されている過去履歴を渡しているので、過去履歴を踏まえたやりとりが可能となっておりますが、過去履歴を渡さずに API コールをすれば、必ず入力したプロンプトだけを踏まえた回答となります。 + +### APIの選択 +ルームとして「 (none) 」を選択すると、右横に使用する API を選択する機能を表示します。 +「Cohere v2 chat 互換 API」または「Cohere OpenAI Chat Completions 互換 API」が選択できます。 + +### ルームなしの設定 +しているときに、左下の歯車アイコンをクリックすると、推論パラメタの編集が可能です。 +1. [ ルーム名 ] は、「 (none) 」固定です。 +2. [ ランダム性 ] は、Temperature と呼ばれるもので、0.0~1.0の範囲で0.1刻みで指定可能です。0.0を指定すると毎回同じ回答になり、1.0に近づくにつれてLLMはより様々な探索を行う可能性が高くなります。 +3. [ システムプロンプト ] は、推論時のシステムプロンプトを指定します。 +4. [ RAG 指定 ] は、推論時に利用する RAG を指定します。なお、RAG不要時は「 none 」を選択します。 +5. [OK] をクリックすると変更した内容でルームを更新します。 + +### マルチモーダル対応 +マルチモーダル対応 + +ルームなし会話では、マルチモーダル対応として、添付画像に対する質問を入力することができます。 +添付できる画像ファイル形式は、png、jpeg、webp、非アニメーションgif形式の5MB以下となります。 +1. 画像の添付方法は、入力欄下の [+] ボタンをクリックしてファイル選択 +2. 画像の添付解除は、画像横の [-] ボタンをクリックして解除 + +マルチモーダル対応は、Cohere v2 Chat 互換 API (/v2/chat)、または、Cohere OpenAI Chat Completions 互換 API (/compatibility/v1/chat/completions) を呼び出すことで実現可能です。 + +### RAG 参照 +ルームなしの設定で RAG を指定したとき、「Cohere v2 chat 互換 API」を選択している場合は、 documents オプションを使って、RAG の検索結果を参照情報として設定して推論します。 +「Cohere OpenAI Chat Completions 互換 API」を選択している場合は、 documents オプションがないため、入力プロンプトの先頭に追加して推論します。 + +## 5. RAG関連の操作 ### RAGの新規作成 [ ファイル ]-[RAG]-[ 新規作成 ] メニューをクリックして、ルームの新規作成画面を表示します。 @@ -106,35 +141,6 @@ RAG自体を削除したり、RAGに格納されているデータを変更し [ RAG管理 ] 画面で [ 削除 ] をクリックすると該当行のRAGを削除できます。 - 削除前に確認ダイアログが表示されるので、誤操作でクリックしてしまった時は [ いいえ ] をクリックすると削除を中止できます。 -## ルームなし会話 -ルームを作成することで下記のような機能をWebAPIとして提供しています。 -- 過去履歴を踏まえたプロンプトパターンの実現 -- ルームへ会話を記録することによる後日閲覧の実現 -- 各種トークン数の設定 -しかしながら、ルーム不要でTakaneに対してプロンプトを入力し回答を得るだけでよい場合には、ルームの設定や管理などの事前実行が必要なWebAPI呼び出しは煩雑に感じてしまいます。 -そのようなユースケースでは「ルームなし会話」モードがあります。 - -### 本サンプル画面での使い方 -ルームの選択で「(none)」を選択するとチャットルームなしでの利用となります。 -サンプルコード内で画面に表示されている過去履歴を渡しているので、過去履歴を踏まえたやりとりが可能となっておりますが、過去履歴を渡さずにAPIコールをすれば、必ず入力したプロンプトだけを踏まえた回答となります。 - -#### 制限事項 -ルームを「(none)」に選択してる場合は、ルーム編集や新規作成の機能はご利用になれません。 -ルーム編集やルーム新規作成が必要な場合は、他のルームを選択してから実行してください。 - -### マルチモーダル対応 -マルチモーダル対応 - -ルームなし会話では、マルチモーダル対応として、添付画像に対する質問を入力することができます。 -添付できる画像ファイル形式は、png、jpeg、webp、非アニメーションgif形式の5MB以下となります。 -1. 画像の添付方法は、入力欄下の [+] ボタンをクリックしてファイル選択 -2. 画像の添付解除は、画像横の [-] ボタンをクリックして解除 - -マルチモーダル対応は、Cohere v2 Chat 互換 API (/v2/chat)、または、Cohere OpenAI Chat Completions 互換 API (/compatibility/v1/chat/completions) を呼び出すことで実現可能です。 -サンプルコードでは、Cohere v2 Chat 互換 API を使用しています。 - -OpenAI 互換 API、または、Cohere V2 Chat 互換 API を呼び出すことで実現可能です。 - --- # サンプルコードの利用 API サンプルコードでは、Fujitsu Cloud Service Genearative AI Platform が提供する次の API を使用しています。 @@ -153,7 +159,9 @@ OpenAI 互換 API、または、Cohere V2 Chat 互換 API を呼び出すこと | POST | /api/v1/chats/{id}/messages/createNextAiMessage | LLM から回答受信 | | GET | /api/v1/chats/{id} | チャットルーム履歴取得 | | POST | /api/v1/action/defined/text:simple_chat/call | チャットルームなし推論 | -| POST | /api/v1/pass-through/takane/v2/chat | Cohere v2 Chat 互換 API (画像付き入力推論) | +| POST | /api/v1/pass-through/takane/v2/chat | Cohere v2 Chat 互換 API | +| POST | /api/v1/pass-through/takane/compatibility/v1/chat/completions | Cohere OpenAI Chat Completions 互換 API | +| POST | /api/v1/retrievers/search | 入力プロンプトからRAG検索 | ## チャットルーム関連 チャットルームの作成・更新・削除は、 MVVM デザインパターンに基づき、 Models\ChatRoomModel.cs に記載しています。 @@ -169,14 +177,14 @@ RAG 関連については、 MVVM デザインパターンに基づき、 Models | 操作 | エンドポイント | 用途 | | ---- | ---- | ---- | -| GET | /api/v1/retrievers | リトリーバー一覧取得 | -| PUT | /api/v1/retrievers/{id} | リトリーバー取得 | -| POST | /api/v1/retrievers | リトリーバー追加 | | POST | /api/v1/files/from-url | URL からのファイルアップロード | | POST | /api/v1/files | ローカルファイルのアップロード | +| DELETE | /api/v1/files/{id} | ファイル削除 | +| GET | /api/v1/retrievers | リトリーバー一覧取得 | +| POST | /api/v1/retrievers | リトリーバー追加 | | POST | /api/v1/retrievers/{id}/process/embeddings | RAGデータ作成 | | GET | /api/v1/retrievers/{id} | RAGデータ作成完了確認 | -| DELETE | /api/v1/retrievers/{id} | リトリーバー削除 | +| PUT | /api/v1/retrievers/{id} | リトリーバー取得 | | DELETE | /api/v1/retrievers/{id} | リトリーバー削除 | --- diff --git a/SampleCSharpUI/Commons/APIData.cs b/SampleCSharpUI/Commons/APIData.cs index ede1bf3..5d91aa4 100644 --- a/SampleCSharpUI/Commons/APIData.cs +++ b/SampleCSharpUI/Commons/APIData.cs @@ -220,7 +220,8 @@ public class TCohereV2ChatRequest public float temperature { get; set; } [DataMember] public uint max_tokens { get; set; } - + [DataMember(EmitDefaultValue = false)] + public TDocument[] documents { get; set; } } [DataContract] @@ -230,6 +231,8 @@ public class TCohereV2ChatMessage public string role { get; set; } [DataMember] public TContent[] content { get; set; } + [DataMember(EmitDefaultValue = false)] + public TCitation[] citations { get; set; } } [DataContract] @@ -239,17 +242,37 @@ public class TContent public string type { get; set; } [DataMember] public string text { get; set; } - [DataMember] + [DataMember(EmitDefaultValue = false)] public TImageUrl image_url { get; set; } } [DataContract] public class TImageUrl { - [DataMember] + [DataMember(EmitDefaultValue = false)] public string url { get; set; } } + [DataContract] + public class TCitation + { + [DataMember(EmitDefaultValue = false)] + public string text { get; set; } + [DataMember(EmitDefaultValue = false)] + public TSource[] sources { get; set; } + } + + [DataContract] + public class TSource + { + [DataMember(EmitDefaultValue = false)] + public string id { get; set; } + [DataMember(EmitDefaultValue = false)] + public string type { get; set; } + [DataMember(EmitDefaultValue = false)] + public TData document { get; set; } + } + [DataContract] public class TCohereV2ChatResponse { @@ -259,7 +282,101 @@ public class TCohereV2ChatResponse public TCohereV2ChatMessage message { get; set; } [DataMember] public string finish_reason { get; set; } + } + + [DataContract] + public class TDocument + { + [DataMember(EmitDefaultValue = false)] + public TData data { get; set; } + } + + [DataContract] + public class TData + { + [DataMember(EmitDefaultValue = false)] + public string id { get; set; } + [DataMember(EmitDefaultValue = false)] + public string title { get; set; } + [DataMember(EmitDefaultValue = false)] + public string text { get; set; } + } + [DataContract] + public class TOpenAIChatRequest + { + [DataMember] + public string model { get; set; } + [DataMember] + public TOpenAIChatMessage[] messages { get; set; } + [DataMember] + public float temperature { get; set; } + [DataMember] + public uint max_tokens { get; set; } + } + [DataContract] + public class TOpenAIChatMessage + { + [DataMember] + public string role { get; set; } + [DataMember] + public string content { get; set; } + } + + [DataContract] + public class TOpenAIChatResponse + { + [DataMember] + public string id { get; set; } + [DataMember] + public TChoice[] choices { get; set; } + [DataMember] + public int created { get; set; } + } + [DataContract] + public class TChoice + { + [DataMember] + public string finish_reason { get; set; } + [DataMember] + public int index { get; set; } + [DataMember] + public TOpenAIChatMessage message { get; set; } + } + + [DataContract] + public class TRetrieverSearchRequest + { + [DataMember] + public string search_type { get; set; } + [DataMember] + public string search_text { get; set; } + [DataMember] + public int limit { get; set; } + [DataMember] + public TRetrieverFilter filter { get; set; } + } + + [DataContract] + public class TRetrieverFilter + { + [DataMember] + public TRetrieverFilterQuery retriever_id { get; set; } + } + + [DataContract] + public class TRetrieverFilterQuery + { + [DataMember(Name = "in")] + public string[] ids { get; set; } + } + + + [DataContract] + public class TRetrieverSearchResponse + { + [DataMember] + public TRefChunks[] results { get; set; } } } } diff --git a/SampleCSharpUI/Commons/Config.cs b/SampleCSharpUI/Commons/Config.cs index b3e6d2d..cefd0bd 100644 --- a/SampleCSharpUI/Commons/Config.cs +++ b/SampleCSharpUI/Commons/Config.cs @@ -5,12 +5,10 @@ namespace SampleCSharpUI.Commons { - internal class Config + internal partial class Config { internal static string ClientId { get; set; } internal static string TenantName { get; set; } - internal static string SelectedChatRoomID { get; set; } - internal static bool IsUseOSWebView { get; set; } internal static bool IsPromptAuthentication { get; set; } internal static string ClientSecret { get; set; } @@ -49,7 +47,6 @@ internal static void LoadProperties() Config.ClientId = recvData.ClientId; Config.TenantName = recvData.TenantName; - Config.SelectedChatRoomID = recvData.SelectedChatRoomID; Config.IsUseOSWebView = recvData.IsUseOSWebView; Config.IsPromptAuthentication = !recvData.IsNotPromptAuthentication; Config.ClientSecret = CryptographyHelper.DecryptStringe(recvData.ClientSecret); @@ -62,7 +59,6 @@ internal static void LoadProperties() { Config.ClientId = string.Empty; Config.TenantName = string.Empty; - Config.SelectedChatRoomID = string.Empty; Config.IsUseOSWebView = false; Config.IsPromptAuthentication = true; Config.ClientSecret = string.Empty; @@ -113,7 +109,6 @@ internal static void SaveProperties() // Loadがはしらないように内部変数から取得する st.ClientId = Config.ClientId; st.TenantName = Config.TenantName; - st.SelectedChatRoomID = Config.SelectedChatRoomID; st.IsUseOSWebView = Config.IsUseOSWebView; st.IsNotPromptAuthentication = !Config.IsPromptAuthentication; st.ClientSecret = CryptographyHelper.EncryptString(Config.ClientSecret); @@ -148,8 +143,6 @@ private class TPropertiesData [DataMember] public string TenantName { get; set; } [DataMember] - public string SelectedChatRoomID { get; set; } - [DataMember] public bool IsUseOSWebView { get; set; } [DataMember] public bool IsNotPromptAuthentication { get; set; } diff --git a/SampleCSharpUI/Commons/ConfigSpecific.cs b/SampleCSharpUI/Commons/ConfigSpecific.cs new file mode 100644 index 0000000..2b47066 --- /dev/null +++ b/SampleCSharpUI/Commons/ConfigSpecific.cs @@ -0,0 +1,211 @@ +using SampleCSharpUI.Commons; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading.Tasks; + +namespace SampleCSharpUI.Commons +{ + internal static partial class Config + { + // 使用中チャットルームID + internal static string SelectedChatRoomID + { + get + { + LoadSpecificProperties(); + return RecData.SelectedChatRoomID; + } + set + { + RecData.SelectedChatRoomID = value; + SaveSpecificProperties(); + } + } + + // システムプロンプト + internal static string SystemPrompt + { + get + { + LoadSpecificProperties(); + return RecData.SystemPrompt; + } + set + { + RecData.SystemPrompt = value; + SaveSpecificProperties(); + } + } + + // チャットルームなし Temperature + internal static float Temperature + { + get + { + LoadSpecificProperties(); + return RecData.Temperature; + } + set + { + RecData.Temperature = value; + SaveSpecificProperties(); + } + } + + // チャットルームなし MaxTokens + internal static int MaxTokens + { + get + { + LoadSpecificProperties(); + return RecData.MaxTokens; + } + set + { + RecData.MaxTokens = value; + SaveSpecificProperties(); + } + } + + // チャットルームなし RAG + internal static string RetrieverID + { + get + { + LoadSpecificProperties(); + return RecData.RetrieverID; + } + set + { + RecData.RetrieverID = value; + SaveSpecificProperties(); + } + } + + private static TSpecificPropertiesData RecData = new TSpecificPropertiesData() + { + SelectedChatRoomID = string.Empty, + Temperature = 0.7f, + MaxTokens = 1024, + }; + + /// + /// プロパティ値の取得 + /// + internal static void LoadSpecificProperties() + { + //保存元のファイル名 + var dataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var settingPath = dataPath + "\\FujitsuGAP\\Settings"; + var fileName = settingPath + "\\Properties_SampleCSharpUI.json"; + + try + { + if (System.IO.File.Exists(fileName)) + { + using (var sr = new System.IO.StreamReader(fileName, new System.Text.UTF8Encoding(false))) + { + var items = new TSpecificPropertiesData(); + var jsonString = sr.ReadToEnd(); + using (var ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString))) + { + var serializer = new DataContractJsonSerializer(typeof(TSpecificPropertiesData)); + RecData = (TSpecificPropertiesData)serializer.ReadObject(ms); + } + } + } + else + { + RecData = new TSpecificPropertiesData() + { + SelectedChatRoomID = string.Empty, + SystemPrompt = "以下は、人間とAIの親しみやすい会話です。このAIはおしゃべり好きで、文脈に基づいて多くの具体的な詳細を伝えてくれます。質問の答えがわからない場合、AIは正直に「わからない」と答えます。", + Temperature = 0.3f, // Takane 省略値 + MaxTokens = 8192, // Takane 省略値 + RetrieverID = string.Empty, + }; + } + } + catch { } + } + + /// + /// プロパティ値の保存 + /// + internal static void SaveSpecificProperties() + { + try + { + //保存元のファイル名 + var dataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var settingPath = dataPath + "\\FujitsuGAP\\Settings"; + var fileName = settingPath + "\\Properties_SampleCSharpUI.json"; + + // フォルダ (ディレクトリ) が存在しているかどうか確認する + if (System.IO.Directory.Exists(settingPath) == false) + { + // フォルダ (ディレクトリ) を作成する + System.IO.Directory.CreateDirectory(settingPath); + } + + // ファイルが存在する場合 + if (System.IO.File.Exists(fileName) == true) + { + //ファイルの属性を取得する + var attr = System.IO.File.GetAttributes(fileName); + + //読み取り専用属性の場合 + if ((attr & System.IO.FileAttributes.ReadOnly) == System.IO.FileAttributes.ReadOnly) + { + //読み取り専用属性を削除する + System.IO.File.SetAttributes(fileName, attr & (~System.IO.FileAttributes.ReadOnly)); + } + } + + //書き込むファイルを開く + using (var ms = new MemoryStream()) + { + var serializer = new DataContractJsonSerializer(typeof(TSpecificPropertiesData)); + using (var writer = JsonReaderWriterFactory.CreateJsonWriter(ms, System.Text.Encoding.UTF8, true, true, " ")) + { + serializer.WriteObject(writer, RecData); + writer.Flush(); + var jsonString = System.Text.Encoding.UTF8.GetString(ms.ToArray()); + using (var sw = new System.IO.StreamWriter(fileName, false, new System.Text.UTF8Encoding(false))) + { + sw.WriteLine(jsonString); + + //ファイルを確実にクローズしてファイル破損を防ぐ + sw.Flush(); + sw.Close(); + } + } + } + } + catch { } + } + + /// + /// プロパティファイルフォーマット + /// + [DataContract] + private class TSpecificPropertiesData + { + [DataMember] + public string SelectedChatRoomID { get; set; } + [DataMember] + public string SystemPrompt { get; set; } + [DataMember] + public float Temperature { get; set; } + [DataMember] + public int MaxTokens { get; set; } + [DataMember] + public string RetrieverID { get; set; } + } + } +} diff --git a/SampleCSharpUI/Converters/StringToGridLengthConverter.cs b/SampleCSharpUI/Converters/StringToGridLengthConverter.cs new file mode 100644 index 0000000..8759aab --- /dev/null +++ b/SampleCSharpUI/Converters/StringToGridLengthConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace SampleCSharpUI.Converters +{ + public class StringToGridLengthConverter : IValueConverter + { + // ConverterParameter に行の標準高さ(例: 42)を渡せます。 + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var id = value as string; + double height = 42; + if (parameter != null) + { + double.TryParse(parameter.ToString(), out var p); + height = p; + } + + if (string.IsNullOrEmpty(id)) + { + if (height > 0) + { + height = 0; + } + else + { + height = -height; + } + return new GridLength(height); // 空文字なら高さ 0(行を消す) + } + else + { + if (height < 0) + { + height = 0; + } + return new GridLength(height); // ID があれば元の高さを返す + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/SampleCSharpUI/Models/ChatModel.cs b/SampleCSharpUI/Models/ChatModel.cs index a33fd72..f30e755 100644 --- a/SampleCSharpUI/Models/ChatModel.cs +++ b/SampleCSharpUI/Models/ChatModel.cs @@ -5,6 +5,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -13,6 +14,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Threading; +using static SampleCSharpUI.Commons.APIData; namespace SampleCSharpUI.Models { @@ -135,12 +137,58 @@ internal async Task DisconnectAsync() } #endregion + #region "チャット関連プロパティ" + public ObservableCollection ChatRooms { get; set; } = new ObservableCollection(); + public ObservableCollection Messages { get; set; } = new ObservableCollection(); + public ObservableCollection APIs { get; set; } = new ObservableCollection() + { + new TItem { ID = "v2", Name = "Cohere v2", FullName = "Cohere v2 Chat API" }, + new TItem { ID = "OpenAI", Name = "Cohere OpenAI", FullName = "Cohere OpenAI compatibility Chat API" } + }; + + private Models.TDataChatRoom _SelectedChatRoom = null; + public Models.TDataChatRoom SelectedChatRoom + { + get { return this._SelectedChatRoom; } + set + { + if (this._SelectedChatRoom != value) + { + this._SelectedChatRoom = value; + OnPropertyChanged(); + + // 選択済チャットルームID保存 + if (this._SelectedChatRoom != null) + { + Config.SelectedChatRoomID = this._SelectedChatRoom?.ID; + Config.SaveProperties(); + } + } + } + } + + private TItem _SelectedAPI = null; + public TItem SelectedAPI + { + get { return _SelectedAPI; } + set + { + if (_SelectedAPI != value) + { + _SelectedAPI = value; + OnPropertyChanged(); + } + } + } + #endregion + /// /// コンストラクター /// public ChatModel() { this.ExpireTimer.Stop(); + this.SelectedAPI = this.APIs[0]; // 認証キー期限切れ対応 this.ExpireTimer.Tick += async (s, e) => @@ -176,32 +224,8 @@ public ChatModel() } #region "チャット関連" - public ObservableCollection ChatRooms { get; set; } = new ObservableCollection(); - public ObservableCollection Messages { get; set; } = new ObservableCollection(); - - private Models.TDataChatRoom _SelectedChatRoom = null; - public Models.TDataChatRoom SelectedChatRoom - { - get { return this._SelectedChatRoom; } - set - { - if (this._SelectedChatRoom != value) - { - this._SelectedChatRoom = value; - OnPropertyChanged(); - - // 選択済チャットルームID保存 - if (this._SelectedChatRoom != null) - { - Config.SelectedChatRoomID = this._SelectedChatRoom?.ID; - Config.SaveProperties(); - } - } - } - } - // チャットルーム一覧取得処理 - internal async Task GetChatRoomsAsync(bool isUseNone = false) + internal async Task GetChatRoomsAsync(bool isUseNone = true) { const string defaultChatRoomName = "General Use"; var defaultChatRoomID = string.Empty; @@ -428,11 +452,11 @@ internal async Task ClearChatRoomAsync(string id) } /// - /// プロンプト入力 + /// プロンプト入力(チャットルームあり) /// /// ルームID /// 入力 - internal async Task SendMessageAsync(string id, string inputText) + internal async Task SendRoomMessageAsync(string id, string inputText) { var content = inputText ?? string.Empty; if (!string.IsNullOrWhiteSpace(content)) @@ -511,7 +535,7 @@ internal async Task SendMessageAsync(string id, string inputText) } /// - /// プロンプト入力(Stream受信) + /// プロンプト入力(チャットルームあり:Stream受信) /// /// ルームID /// 入力 @@ -654,7 +678,7 @@ internal async Task SendRoomMessageStreamingAsync(string id, string inputText) this.IsStreaming = true; jsonString = await HttpHelper.PostRequestStreamAsync(msgId, $"/api/v1/chats/{id}/messages/createNextAiMessage/streaming", this.IdToken, "{}"); } - catch (Exception ex) + catch { this.IsStreaming = false; @@ -690,17 +714,17 @@ private async Task GetLastAIResponseAsync(string id) } /// - /// プロンプト入力(チャットルームなし) + /// プロンプト入力(チャットルームなし:GAP_SimpleChat API) /// /// 入力 - internal async Task SendMessageAsync(List histories, float temperature, int token, string inputText) + internal async Task SendMessageSimpleChatAsync(List histories, float temperature, int token, string inputText, string systemPrompt) { var content = inputText ?? string.Empty; if (!string.IsNullOrWhiteSpace(content)) { var body = new APIData.TNonRoomRequest() { - messages = this.SetHistories(histories), + messages = this.SetSimpleChatHistories(histories), question = content, model = "cohere.command-r-plus-fujitsu", max_tokens = token, @@ -756,7 +780,7 @@ internal async Task SendMessageAsync(List histories, float temperature } // 履歴設定(チャットルームなし) - private APIData.TChatMessage[] SetHistories(List histories) + private APIData.TChatMessage[] SetSimpleChatHistories(List histories) { if (histories == null || histories.Count == 0) { @@ -780,30 +804,7 @@ private APIData.TChatMessage[] SetHistories(List histories) } } - // 履歴設定(チャットルームなし) - private APIData.TCohereV2ChatMessage[] SetCohereV2ChatHistories(List histories) - { - if (histories == null || histories.Count == 0) - { - return new APIData.TCohereV2ChatMessage[] { }; - } - else - { - var messages = new APIData.TCohereV2ChatMessage[histories.Count]; - for (int i = 0; i < histories.Count; i++) - { - var history = histories[i]; - messages[i] = new APIData.TCohereV2ChatMessage() - { - role = history.Role == "ai" ? "assistant" : "user", - content = new APIData.TContent[] { new APIData.TContent() { type = "text", text = history.Content } }, - }; - } - return messages; - } - } - - // メッセージ追加(チャットルームなし) + // メッセージ追加 private Guid SetMessage(string role, string content, DateTime time, List refs, string image = null) { var msg = new TMessage() @@ -818,6 +819,8 @@ private Guid SetMessage(string role, string content, DateTime time, List this.Messages.Add(msg); return msg.Id; } + + // メッセージ更新 private Guid SetMessage(Guid id, string role, string content, DateTime time, List refs) { // 指定された ID を持つ既存メッセージを検索 @@ -840,16 +843,16 @@ private Guid SetMessage(Guid id, string role, string content, DateTime time, Lis } } - /// - /// プロンプト入力(チャットルームなし/マルチモーダル) + /// プロンプト入力(チャットルームなし/マルチモーダル:Cohere v2 chat API) /// /// 入力 - internal async Task SendMessageWithFileAsync(List histories, float temperature, int token, string inputText, string filePath) + internal async Task SendMessageCohereV2ChatAsync(List histories, float temperature, int token, string inputText, string systemPrompt, string retrieverID, string filePath) { - var content = inputText ?? string.Empty; + var content = inputText?.Trim() ?? string.Empty; + var refs = new List(); var base64ImageData = !string.IsNullOrEmpty(filePath) ? await Base64Helper.ImageFileToBase64Async(filePath) : string.Empty; - if (!string.IsNullOrWhiteSpace(content)) + if (!string.IsNullOrEmpty(content)) { var body = new APIData.TCohereV2ChatRequest() { @@ -859,38 +862,74 @@ internal async Task SendMessageWithFileAsync(List histories, float tem max_tokens = (uint)token, }; - // ここで配列の末尾に要素を追加する(body.messages が配列であることを前提) - var existing = body.messages ?? new APIData.TCohereV2ChatMessage[] { }; - var newArr = new APIData.TCohereV2ChatMessage[existing.Length + 1]; - if (existing.Length > 0) + // 既存メッセージをリスト化して末尾に要素を追加する + var list = new System.Collections.Generic.List(); + if (body.messages != null && body.messages.Length > 0) { - Array.Copy(existing, newArr, existing.Length); + list.AddRange(body.messages); } + + // システムプロンプト追加 + if (!string.IsNullOrEmpty(systemPrompt)) + { + list.Add(new APIData.TCohereV2ChatMessage() + { + role = "system", + content = new APIData.TContent[] { + new APIData.TContent() { type = "text", text = systemPrompt }, + }, + }); + } + + // 参照情報追加(Cohere v2 chat APIでは、参照情報をdocumentsオプションとして設定する) + if (!string.IsNullOrEmpty(retrieverID)) + { + var refChunks = await this.SearchRetrieverAsync(retrieverID, content, 10); + if (refChunks != null && refChunks.Length > 0) + { + // 参照情報をリストに追加 + refs.AddRange(refChunks?.Select(chunk => (chunk?.text?.Replace("\n\n", "\n") as string) ?? string.Empty) ?? Enumerable.Empty()); + + // documentsオプションに参照情報を設定 + body.documents = refChunks?.Select(refChunk => new APIData.TDocument + { + data = new APIData.TData + { + id = Guid.NewGuid().ToString(), + title = string.Empty, + text = refChunk?.text?.Replace("\n\n", "\n") ?? string.Empty, + } + }).ToArray(); + } + } + + // 入力プロンプト追加 if (!string.IsNullOrEmpty(base64ImageData)) { - newArr[newArr.Length - 1] = new APIData.TCohereV2ChatMessage() + list.Add(new APIData.TCohereV2ChatMessage() { role = "user", content = new APIData.TContent[] { - new APIData.TContent() { type = "text", text = inputText }, + new APIData.TContent() { type = "text", text = content }, new APIData.TContent() { type = "image_url", image_url = new APIData.TImageUrl() { url= $"data:image/png;base64,{base64ImageData}" } } }, - }; + }); } else { - newArr[newArr.Length - 1] = new APIData.TCohereV2ChatMessage() + list.Add(new APIData.TCohereV2ChatMessage() { role = "user", content = new APIData.TContent[] { - new APIData.TContent() { type = "text", text = inputText }, + new APIData.TContent() { type = "text", text = content }, }, - }; + }); } - body.messages = newArr; - // 質問のメッセージ追加 - this.SetMessage("user", content, DateTime.UtcNow.ToLocalTime(), new List(), filePath); + body.messages = list.ToArray(); + + // 質問のメッセージ追加(参照情報は表示しない) + this.SetMessage("user", inputText, DateTime.UtcNow.ToLocalTime(), new List(), filePath); // ここで body を JSON 文字列にシリアライズして変数に格納する using (var ms = new MemoryStream()) @@ -912,7 +951,155 @@ internal async Task SendMessageWithFileAsync(List histories, float tem var item = ser.ReadObject(json) as APIData.TCohereV2ChatResponse; if (item?.id != null) { - this.SetMessage(msgId, "ai", item.message.content[0].text, DateTime.UtcNow.ToLocalTime(), new List()); + if (item.message?.citations != null) + { + // 参照情報がある場合は、回答のメッセージに参照情報を追加する + foreach (var refItem in item.message?.citations) + { + Debug.WriteLine($"{refItem.text} in {string.Join(",", refItem.sources.Select((x) => x?.id).Where(id => !string.IsNullOrEmpty(id)))}"); + } + } + this.SetMessage(msgId, "ai", item.message.content[0].text, DateTime.UtcNow.ToLocalTime(), refs); + } + else + { + throw new Exception(jsonString); + } + } + json.Close(); + + // 最新行表示 + OnPropertyChanged("Messages_Item"); + } + } + catch (Exception ex) + { + // エラー発生時はAI回答欄を削除し、exceptionを投げる + this.Messages.Remove(this.Messages.Where((x) => x.Id == msgId).FirstOrDefault()); + throw ex; + } + } + } + } + } + private APIData.TCohereV2ChatMessage[] SetCohereV2ChatHistories(List histories) + { + if (histories == null || histories.Count == 0) + { + return new APIData.TCohereV2ChatMessage[] { }; + } + else + { + var messages = new APIData.TCohereV2ChatMessage[histories.Count]; + for (int i = 0; i < histories.Count; i++) + { + var history = histories[i]; + messages[i] = new APIData.TCohereV2ChatMessage() + { + role = history.Role == "ai" ? "assistant" : "user", + content = new APIData.TContent[] { new APIData.TContent() { type = "text", text = history.Content } }, + }; + } + return messages; + } + } + + /// + /// プロンプト入力(チャットルームなし/マルチモーダル:Cohere OpenAI 互換 API) + /// + /// 入力 + internal async Task SendMessageOpenAIChatAsync(List histories, float temperature, int token, string inputText, string systemPrompt, string retrieverID, string filePath) + { + var content = inputText?.Trim() ?? string.Empty; + var refs = new List(); + var base64ImageData = !string.IsNullOrEmpty(filePath) ? await Base64Helper.ImageFileToBase64Async(filePath) : string.Empty; + if (!string.IsNullOrEmpty(content)) + { + var body = new APIData.TCohereV2ChatRequest() + { + model = "takane", + messages = this.SetCohereV2ChatHistories(histories), + temperature = temperature, + max_tokens = (uint)token, + }; + + // 既存メッセージをリスト化して末尾に要素を追加する + var list = new System.Collections.Generic.List(); + if (body.messages != null && body.messages.Length > 0) + { + list.AddRange(body.messages); + } + + // システムプロンプト追加 + if (!string.IsNullOrEmpty(systemPrompt)) + { + list.Add(new APIData.TCohereV2ChatMessage() + { + role = "system", + content = new APIData.TContent[] { new APIData.TContent() { type = "text", text = systemPrompt } }, + }); + } + + // 参照情報追加 (OpenAI 互換 API では、参照情報を質問文に組み込む) + if (!string.IsNullOrEmpty(retrieverID)) + { + var refChunks = await this.SearchRetrieverAsync(retrieverID, content, 10); + if (refChunks != null && refChunks.Length > 0) + { + refs.AddRange(refChunks?.Select(chunk => (chunk?.text.Replace("\n\n", "\n") as string) ?? string.Empty) ?? Enumerable.Empty()); + content = $"{string.Join("\n", refs)}\n\nQuestion:{content}"; + } + } + + // 入力プロンプト追加 + if (!string.IsNullOrEmpty(base64ImageData)) + { + list.Add(new APIData.TCohereV2ChatMessage() + { + role = "user", + content = new APIData.TContent[] { + new APIData.TContent() { type = "text", text = content }, + new APIData.TContent() { type = "image_url", image_url = new APIData.TImageUrl() { url= $"data:image/png;base64,{base64ImageData}" } } + }, + }); + } + else + { + list.Add(new APIData.TCohereV2ChatMessage() + { + role = "user", + content = new APIData.TContent[] { + new APIData.TContent() { type = "text", text = content }, + }, + }); + } + + body.messages = list.ToArray(); + + // 質問のメッセージ追加(参照情報は表示しない) + this.SetMessage("user", inputText, DateTime.UtcNow.ToLocalTime(), new List(), filePath); + + // ここで body を JSON 文字列にシリアライズして変数に格納する + using (var ms = new MemoryStream()) + { + var msgId = this.SetMessage("ai", Resources.Streaming, DateTime.UtcNow.ToLocalTime(), new List()); + OnPropertyChanged("Messages_Item"); + var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(APIData.TCohereV2ChatRequest)); + { + serializer.WriteObject(ms, body); + try + { + var bodyJsonString = Encoding.UTF8.GetString(ms.ToArray()); + var jsonString = await HttpHelper.PostRequestAsync($"/api/v1/pass-through/takane/compatibility/v1/chat/completions", this.IdToken, bodyJsonString); + using (var json = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString))) + { + var ser = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(APIData.TOpenAIChatResponse)); + { + // 回答のメッセージ追加 + var item = ser.ReadObject(json) as APIData.TOpenAIChatResponse; + if (item?.id != null) + { + this.SetMessage(msgId, "ai", item.choices[0].message.content, DateTime.UtcNow.ToLocalTime(), refs); } else { @@ -936,6 +1123,48 @@ internal async Task SendMessageWithFileAsync(List histories, float tem } } + /// + /// リトリーバー検索 + /// + /// + /// + /// + /// + internal async Task SearchRetrieverAsync(string id, string content, int limit = 10) + { + TRefChunks[] refChunks = null; + var body = new TRetrieverSearchRequest() + { + search_type = "similarity_search", + search_text = content, + limit = limit, + filter = new TRetrieverFilter() + { + retriever_id = new TRetrieverFilterQuery() { ids = new string[] { id } } + } + }; + + // ここで body を JSON 文字列にシリアライズして変数に格納する + using (var ms = new MemoryStream()) + { + var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(TRetrieverSearchRequest)); + { + serializer.WriteObject(ms, body); + var bodyJsonString = Encoding.UTF8.GetString(ms.ToArray()); + var jsonString = await HttpHelper.PostRequestAsync("/api/v1/retrievers/search", this.IdToken, bodyJsonString); + using (var json = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString))) + { + var ser = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(TRetrieverSearchResponse)); + { + var result = ser.ReadObject(json) as TRetrieverSearchResponse; + refChunks = result?.results ?? new TRefChunks[0]; + json.Close(); + } + } + } + } + return refChunks; + } /// /// 最新の入力プロンプトまでを削除する(AIからの回答がある場合は、その回答まで削除する @@ -1037,20 +1266,28 @@ internal async Task SendAsync(string content, string filePath) await Task.Delay(100); } } - else if (string.IsNullOrEmpty(filePath)) + else if (string.IsNullOrEmpty(filePath) && string.IsNullOrEmpty(Config.RetrieverID)) { - await this.SendMessageAsync(this.Messages.ToList(), (float)0.5, 1024, content); + await this.SendMessageSimpleChatAsync(this.Messages.ToList(), Config.Temperature, Config.MaxTokens, content, Config.SystemPrompt); OnPropertyChanged("IsStreaming"); } else { - await this.SendMessageWithFileAsync(this.Messages.ToList(), (float)0.5, 1024, content, filePath); + if (this.SelectedAPI?.ID == "v2") + { + await this.SendMessageCohereV2ChatAsync(this.Messages.ToList(), Config.Temperature, Config.MaxTokens, content, Config.SystemPrompt, Config.RetrieverID, filePath); + } + else + { + await this.SendMessageOpenAIChatAsync(this.Messages.ToList(), Config.Temperature, Config.MaxTokens, content, Config.SystemPrompt, Config.RetrieverID, filePath); + + } OnPropertyChanged("IsStreaming"); } } catch { - // 追加失敗時は無視(必要であればログ追加) + throw; } } } @@ -1091,4 +1328,11 @@ public class TDataRetriever public string[] OriginIDs { get; set; } = null; public DateTime CreateDateTime { get; set; } = DateTime.Now; } + + public class TItem + { + public string ID { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string FullName { get; set; } = string.Empty; + } } \ No newline at end of file diff --git a/SampleCSharpUI/Models/ChatRoomModel.cs b/SampleCSharpUI/Models/ChatRoomModel.cs index 3199b4f..b34ecda 100644 --- a/SampleCSharpUI/Models/ChatRoomModel.cs +++ b/SampleCSharpUI/Models/ChatRoomModel.cs @@ -49,6 +49,20 @@ public string ChatRoomName } } + /// + /// システムプロンプトルーム名 + /// + private string _SystemPrompt = string.Empty; + public string SystemPrompt + { + get { return _SystemPrompt; } + set + { + _SystemPrompt = value; + OnPropertyChanged(); + } + } + /// /// ランダム性 /// @@ -105,6 +119,20 @@ public int AnswerTokenBudget } } + /// + /// 最大トークン(Takane互換APIでは、回答トークン) + /// + private int _MaxTokens = 8192; + public int MaxTokens + { + get { return _MaxTokens; } + set + { + _MaxTokens = value; + OnPropertyChanged(); + } + } + /// /// コンストラクター /// @@ -120,25 +148,48 @@ public ChatRoomModel() internal async Task GetChatRoomAsync(string id, List retrievers) { var selectedRetriever = new TDataRetriever(); - var jsonString = await HttpHelper.GetRequestAsync($"/api/v1/chats/{id}", this.IdToken); - using (var json = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString))) + + // チャットルーム情報を取得 + if (!string.IsNullOrEmpty(id)) { - var ser = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(APIData.TChat)); + // チャットルームIDが指定されている場合は、チャットルームから取得 + var jsonString = await HttpHelper.GetRequestAsync($"/api/v1/chats/{id}", this.IdToken); + using (var json = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonString))) { - this.ChatRoomData = ser.ReadObject(json) as APIData.TChat; - - // 取得値設定 - this.ChatRoomName = this.ChatRoomData.name.Trim(); - this.Temperature = this.ChatRoomData.chat_setting.temperature; - this.HistoryTokenBudget = this.ChatRoomData.chat_setting.tokens_budget.history; - this.DocumentTokenBudget = this.ChatRoomData.chat_setting.tokens_budget.documents; - this.AnswerTokenBudget = this.ChatRoomData.chat_setting.tokens_budget.answer; - selectedRetriever = retrievers.FirstOrDefault((x) => x.ID == this.ChatRoomData.retriever_ids.FirstOrDefault()); - if (selectedRetriever == null) + var ser = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(APIData.TChat)); { - selectedRetriever = retrievers[0]; + this.ChatRoomData = ser.ReadObject(json) as APIData.TChat; + + // 取得値設定 + this.ChatRoomName = this.ChatRoomData.name.Trim(); + this.Temperature = this.ChatRoomData.chat_setting.temperature; + this.HistoryTokenBudget = this.ChatRoomData.chat_setting.tokens_budget.history; + this.DocumentTokenBudget = this.ChatRoomData.chat_setting.tokens_budget.documents; + this.AnswerTokenBudget = this.ChatRoomData.chat_setting.tokens_budget.answer; + selectedRetriever = retrievers.FirstOrDefault((x) => x.ID == this.ChatRoomData.retriever_ids?.FirstOrDefault()); + if (selectedRetriever == null) + { + selectedRetriever = retrievers[0]; + } + json.Close(); } - json.Close(); + } + } + else + { + // チャットルームなし時は設定ファイルから取得 + //TODO: 設定ファイルから取得/デフォルト値を設定 + this.ChatRoomName = "(none)"; + this.SystemPrompt = Config.SystemPrompt; + this.Temperature = Config.Temperature; + this.HistoryTokenBudget = 0; // 未使用パラメタ + this.DocumentTokenBudget = 0; // 未使用パラメタ + this.AnswerTokenBudget = 0; // 未使用パラメタ + this.MaxTokens = Config.MaxTokens; + selectedRetriever = retrievers.FirstOrDefault((x) => x.ID == Config.RetrieverID); + if (selectedRetriever == null) + { + selectedRetriever = retrievers[0]; } } return selectedRetriever; @@ -161,35 +212,48 @@ internal async Task CreateChatRoomAsync(string chatRoomName, string retr /// internal async Task SetChatRoomAsync(string retrieverID) { - var id = this.ChatRoomData.id; + var id = this.ChatRoomData?.id; // 更新値設定 - if (!string.IsNullOrEmpty(retrieverID) && this.DocumentTokenBudget == 0) + if (!string.IsNullOrEmpty(id)) { - //history = string.IsNullOrEmpty(retrieverID) ? 125000 : 62500; - //documents = string.IsNullOrEmpty(retrieverID) ? 0 : 62500; - //answer = string.IsNullOrEmpty(retrieverID) ? 2048 : 2048; - this.DocumentTokenBudget = this.HistoryTokenBudget / 2; - this.HistoryTokenBudget = this.HistoryTokenBudget - this.DocumentTokenBudget; - } - this.ChatRoomData.name = this.ChatRoomName.Trim(); - this.ChatRoomData.chat_setting.temperature = this.Temperature; - this.ChatRoomData.chat_setting.tokens_budget.history = this.HistoryTokenBudget; - this.ChatRoomData.chat_setting.tokens_budget.documents = this.DocumentTokenBudget; - this.ChatRoomData.chat_setting.tokens_budget.answer = this.AnswerTokenBudget; - this.ChatRoomData.retriever_ids = string.IsNullOrEmpty(retrieverID) ? new string[] { } : new string[] { retrieverID }; - this.ChatRoomData.chat_template_id = string.IsNullOrEmpty(retrieverID) ? "builtin.chat" : "builtin.document_combine"; + // チャットルームあり:チャットルームに設定値を保存 + if (!string.IsNullOrEmpty(retrieverID) && this.DocumentTokenBudget == 0) + { + //history = string.IsNullOrEmpty(retrieverID) ? 125000 : 62500; + //documents = string.IsNullOrEmpty(retrieverID) ? 0 : 62500; + //answer = string.IsNullOrEmpty(retrieverID) ? 2048 : 2048; + this.DocumentTokenBudget = this.HistoryTokenBudget / 2; + this.HistoryTokenBudget = this.HistoryTokenBudget - this.DocumentTokenBudget; + } + this.ChatRoomData.name = this.ChatRoomName.Trim(); + this.ChatRoomData.chat_setting.temperature = this.Temperature; + this.ChatRoomData.chat_setting.tokens_budget.history = this.HistoryTokenBudget; + this.ChatRoomData.chat_setting.tokens_budget.documents = this.DocumentTokenBudget; + this.ChatRoomData.chat_setting.tokens_budget.answer = this.AnswerTokenBudget; + this.ChatRoomData.chat_setting.max_tokens = this.AnswerTokenBudget; // TakaneのMax_Tokensは、回答トークンと同じ値を設定する + this.ChatRoomData.retriever_ids = string.IsNullOrEmpty(retrieverID) ? new string[] { } : new string[] { retrieverID }; + this.ChatRoomData.chat_template_id = string.IsNullOrEmpty(retrieverID) ? "builtin.chat" : "builtin.document_combine"; - // 更新APIコール - using (var ms = new MemoryStream()) - { - var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(APIData.TChat)); + // 更新APIコール + using (var ms = new MemoryStream()) { - serializer.WriteObject(ms, this.ChatRoomData); - var bodyJsonString = Encoding.UTF8.GetString(ms.ToArray()); - var jsonString = await HttpHelper.PutRequestAsync($"/api/v1/chats/{id}", this.IdToken, bodyJsonString); + var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(APIData.TChat)); + { + serializer.WriteObject(ms, this.ChatRoomData); + var bodyJsonString = Encoding.UTF8.GetString(ms.ToArray()); + var jsonString = await HttpHelper.PutRequestAsync($"/api/v1/chats/{id}", this.IdToken, bodyJsonString); + } } } + else + { + // チャットルームなし:設定ファイルに保存 + Config.SystemPrompt = this.SystemPrompt; + Config.Temperature = this.Temperature; + Config.MaxTokens = this.MaxTokens; + Config.RetrieverID = retrieverID; + } } /// diff --git a/SampleCSharpUI/Models/RetrieverModel.cs b/SampleCSharpUI/Models/RetrieverModel.cs index 6e72d6f..5f537ab 100644 --- a/SampleCSharpUI/Models/RetrieverModel.cs +++ b/SampleCSharpUI/Models/RetrieverModel.cs @@ -104,7 +104,7 @@ internal async Task GetRetrieversAsync(bool isUseNone = false) var ser = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(APIData.TRetrievers)); { var result = ser.ReadObject(json) as APIData.TRetrievers; - foreach (var item in result.results?.OrderByDescending((x)=> x.created_at)) + foreach (var item in result.results?.OrderByDescending((x) => x.created_at)) { var retriever = new Models.TDataRetriever() { diff --git a/SampleCSharpUI/Properties/AssemblyInfo.cs b/SampleCSharpUI/Properties/AssemblyInfo.cs index da302ce..7eb0c3f 100644 --- a/SampleCSharpUI/Properties/AssemblyInfo.cs +++ b/SampleCSharpUI/Properties/AssemblyInfo.cs @@ -48,5 +48,5 @@ // ビルド番号 // リビジョン // -[assembly: AssemblyVersion("1.3.2.0")] -[assembly: AssemblyFileVersion("1.3.2.0")] +[assembly: AssemblyVersion("1.4.0.0")] +[assembly: AssemblyFileVersion("1.4.0.0")] diff --git a/SampleCSharpUI/Properties/Resources.Designer.cs b/SampleCSharpUI/Properties/Resources.Designer.cs index ed6027a..aa7ccc3 100644 --- a/SampleCSharpUI/Properties/Resources.Designer.cs +++ b/SampleCSharpUI/Properties/Resources.Designer.cs @@ -339,6 +339,15 @@ public static string ManageRetrievers { } } + /// + /// Max_Tokens に類似しているローカライズされた文字列を検索します。 + /// + public static string MaxTokens { + get { + return ResourceManager.GetString("MaxTokens", resourceCulture); + } + } + /// /// バージョン情報(_A) に類似しているローカライズされた文字列を検索します。 /// @@ -600,6 +609,15 @@ public static string Streaming { } } + /// + /// システムプロンプト に類似しているローカライズされた文字列を検索します。 + /// + public static string SystemPrompt { + get { + return ResourceManager.GetString("SystemPrompt", resourceCulture); + } + } + /// /// ランダム性 に類似しているローカライズされた文字列を検索します。 /// diff --git a/SampleCSharpUI/Properties/Resources.resx b/SampleCSharpUI/Properties/Resources.resx index 03fe1b0..cc8038e 100644 --- a/SampleCSharpUI/Properties/Resources.resx +++ b/SampleCSharpUI/Properties/Resources.resx @@ -306,4 +306,10 @@ [ログイン]をクリックして、事前に設定した内容で認証を行ってください。 + + Max_Tokens + + + システムプロンプト + \ No newline at end of file diff --git a/SampleCSharpUI/SampleCSharpUI.csproj b/SampleCSharpUI/SampleCSharpUI.csproj index a2cd0e5..e958f10 100644 --- a/SampleCSharpUI/SampleCSharpUI.csproj +++ b/SampleCSharpUI/SampleCSharpUI.csproj @@ -153,6 +153,7 @@ + @@ -167,6 +168,7 @@ + diff --git a/SampleCSharpUI/ViewModels/MainViewModel.cs b/SampleCSharpUI/ViewModels/MainViewModel.cs index f4d3619..cf8a03c 100644 --- a/SampleCSharpUI/ViewModels/MainViewModel.cs +++ b/SampleCSharpUI/ViewModels/MainViewModel.cs @@ -1,4 +1,5 @@ using SampleCSharpUI.Commons; +using SampleCSharpUI.Models; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -78,6 +79,24 @@ public ObservableCollection Messages set { this.Model.Messages = value; } } + /// + /// API一覧を表すコレクション。 + /// + public ObservableCollection APIs + { + get { return this.Model.APIs; } + set { this.Model.APIs = value; } + } + + /// + /// 利用API + /// + public Models.TItem SelectedAPI + { + get { return this.Model.SelectedAPI; } + set { this.Model.SelectedAPI = value; } + } + /// /// Busy表示用 /// @@ -134,6 +153,9 @@ public string AttachedFilePath } } + /// + /// 添付ファイル名(1ファイルのみ) + /// private string _AttachedFileName; public string AttachedFileName { @@ -148,6 +170,7 @@ public string AttachedFileName } } + /// /// 参照ドキュメント一覧 /// @@ -233,7 +256,7 @@ internal async Task DisconnectAsync() /// ルーム一覧取得 /// /// - internal async Task GetChatRoomsAsync(bool isUseNone = false) + internal async Task GetChatRoomsAsync(bool isUseNone = true) { await this.Model.GetChatRoomsAsync(isUseNone); } @@ -388,6 +411,7 @@ public RelayCommand PreviewKeyDownCommand { // Command内で例外が発生した場合はここでキャッチしてメッセージ表示 OnMessaged(ex.Message); + this.IsBusy = false; } } } diff --git a/SampleCSharpUI/ViewModels/ManageChatRoomsViewModel.cs b/SampleCSharpUI/ViewModels/ManageChatRoomsViewModel.cs index e6fbbcd..8e48d1a 100644 --- a/SampleCSharpUI/ViewModels/ManageChatRoomsViewModel.cs +++ b/SampleCSharpUI/ViewModels/ManageChatRoomsViewModel.cs @@ -82,6 +82,24 @@ public int AnswerTokenBudget set { this.Model.AnswerTokenBudget = value; } } + /// + /// 最大トークン + /// + public int MaxTokens + { + get { return this.Model.MaxTokens; } + set { this.Model.MaxTokens = value; } + } + + /// + /// システムプロンプト + /// + public string SystemPrompt + { + get { return this.Model.SystemPrompt; } + set { this.Model.SystemPrompt = value; } + } + /// /// リトリーバー一覧を表すコレクション /// @@ -187,6 +205,42 @@ public RelayCommand EditRoomCommand } } + /// + /// 設定保存 + /// + RelayCommand _SaveCommand; + public RelayCommand SaveCommand + { + get + { + if (_SaveCommand == null) + { + _SaveCommand = new RelayCommand(async () => + { + this.IsBusy = true; + try + { + await this.Model.SetChatRoomAsync(this.SelectedRetriever.ID); + OnMessaged(""); + } + catch (Exception ex) + { + OnMessaged(ex.Message); + } + finally + { + this.IsBusy = false; + } + }); + } + return _SaveCommand; + } + set + { + _SaveCommand = value; + } + } + /// /// ルーム削除(確認ダイアログあり) /// diff --git a/SampleCSharpUI/Views/EditChatRoomWindow.xaml b/SampleCSharpUI/Views/EditChatRoomWindow.xaml index a14160d..7bf9dbb 100644 --- a/SampleCSharpUI/Views/EditChatRoomWindow.xaml +++ b/SampleCSharpUI/Views/EditChatRoomWindow.xaml @@ -19,6 +19,25 @@ + + + + + @@ -34,48 +53,78 @@ + + + - - + - + @@ -290,7 +291,7 @@ - + @@ -309,6 +310,33 @@ + + + + + + + + + + + + diff --git a/SampleCSharpUI/Views/MainWindow.xaml.cs b/SampleCSharpUI/Views/MainWindow.xaml.cs index b8eeae7..ffb7d71 100644 --- a/SampleCSharpUI/Views/MainWindow.xaml.cs +++ b/SampleCSharpUI/Views/MainWindow.xaml.cs @@ -110,7 +110,7 @@ public MainWindow() { // 編集画面を表示 var subWindow = new EditChatRoomWindow(this.ViewModel.SelectedChatRoom) { Owner = this }; - if (subWindow.ShowDialog() == true) + if (subWindow.ShowDialog() == true && !string.IsNullOrEmpty(App.MainVM.SelectedChatRoom?.ID)) { // チャットルーム一覧を再取得 await this.ViewModel.GetChatRoomsAsync(); diff --git a/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml b/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml index fa192e4..2f15809 100644 --- a/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml +++ b/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml @@ -76,6 +76,16 @@ + + + diff --git a/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml.cs b/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml.cs index 7d8c666..167ccbb 100644 --- a/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml.cs +++ b/SampleCSharpUI/Views/ManageChatRoomsWindow.xaml.cs @@ -37,7 +37,7 @@ public ManageChatRoomsWindow(MainWindow window) this.ViewModel.IsBusy = true; try { - await App.MainVM.GetChatRoomsAsync(true); + await App.MainVM.GetChatRoomsAsync(); } catch (Exception ex) { diff --git a/images/snapshot_01.png b/images/snapshot_01.png index d34bde8..96b0c7f 100644 Binary files a/images/snapshot_01.png and b/images/snapshot_01.png differ