<font size = 6>**Device LLM Agent與VoiceTalk Management**</font> # <font size = 5>1. 前置作業</font> 說明: 1. 建立DF:執行DF_Prompt -> 整理後得DF_Structure.csv -> 以CCM API於IoTtalk中建立DF(下方「CCM API使用」處有程式碼) 2. 建立DM: * sol 1: (需批次建立Device Model時使用) 執行Trait_Prompt與Device_Type_Promp後 -> 整理後得Trait Management.csv與DM Management.csv-> 以CCM API於IoTtalk中建立DM(下方「CCM API使用」處有程式碼)-> 將兩個csv內容加至 /VoiceTalk_library/DB/enUS的同名稱檔案中,以便後續使用VoiceTalk Management頁面中內容一致 * sol 2: (適合少量、要手動增加時) 執行Trait_Prompt與Device_Type_Promp後 -> 整理後得Trait Management.csv與DM Management.csv-> 於VoiceTalk Management頁面中的"Trait"按鈕中,依照Trait_Management.csv內容一一建立Trait-> 於VoiceTalk Management頁面中的"DM"按鈕中,依照DM_Management.csv內容一一建立DM(按下OK按鈕時,後台CCM會自動建立IoTtalk上的DM(可參考下方「Flask route "/SendData"處」)-> 建立完成後 /VoiceTalk_library/DB/enUS中的兩個csv會自動增加新建立的資料 ## <font size = 5>Prompt相關</font> [Device_LLM_Agent GitHub](https://github.com/IoTtalk/VoiceTalk_Library/tree/master/Device_LLM_Agent) GPT_API [金鑰申請方法](https://hackmd.io/@claireshen/Hyo-vn9bel) ```python= os.environ['OPENAI_API_KEY'] = "GPT API金鑰" for Prompt in [All_DF_Prompt, Trait_Prompt, Device_Type_Prompt]: client = OpenAI() response_content = "" stream = client.chat.completions.create( model="gpt-4", messages=[{"role": "user", "content": Prompt}], stream=True, ) for chunk in stream: if chunk.choices[0].delta.content is not None: response_content += chunk.choices[0].delta.content ``` 1. DF_Prompt:"Command"為DF_Structure.csv 的"df_name"欄位中,該csv是 DF_Prompt 結果整理後的資料 ```python= # 擇一使用 All_DF_Prompt = f""" For every Command in Google Home (e.g., BrightnessAbsolute), build a table with the following fields: 1.DF Name: Fill in the Command name (e.g., BrightnessAbsolute). 2.Number of Parameters: Enter the total number of parameters for the Command. 3.For each parameter (i-th parameter), include the following fields: •X-i: Fill with the parameter name (e.g., X1 for the first parameter, X2 for the second parameter, etc.). •Data Type: Specify the type of the parameter (e.g., integer, float, etc.). •Min: Enter the minimum value of the parameter. If no default minimum value is provided, and there is one default value, enter the default value here. •Max: Enter the maximum value of the parameter. If no default maximum value is provided, and there is one default value, enter the default value here. Otherwise, fill in nil. •Unit: Specify the unit of the parameter (if applicable). 4.Comment: Include any additional information about the Command. For example, if the Command defines string labels with values, for every string “s” with value “v”, list it as “String=s, Value=v”. """ DF_Prompt = f""" For the "{command}" Command in Google Home, build a table with the following fields: 1.DF Name: Fill in the Command name (e.g., BrightnessAbsolute). 2.Number of Parameters: Enter the total number of parameters for the Command. 3.For each parameter (i-th parameter), include the following fields: •X-i: Fill with the parameter name (e.g., X1 for the first parameter, X2 for the second parameter, etc.). •Data Type: Specify the type of the parameter (e.g., integer, float, etc.). •Min: Enter the minimum value of the parameter. If no default minimum value is provided, and there is one default value, enter the default value here. •Max: Enter the maximum value of the parameter. If no default maximum value is provided, and there is one default value, enter the default value here. Otherwise, fill in nil. •Unit: Specify the unit of the parameter (if applicable). 4.Comment: Include any additional information about the Command. For example, if the Command defines string labels with values, for every string “s” with value “v”, list it as “String=s, Value=v”. """ ``` 2. Trait_Prompt 結果整理後可得到如下範例(Trait_Management.csv) ```python= Trait_Prompt = """ List all Commands associated with each Trait in Google Home. For example, under the 'Brightness' Trait, list all relevant Commands like 'BrightnessAbsolute.' """ ``` ![image](https://hackmd.io/_uploads/S1__h_tbgl.png) 3. Device_Type_Promt結果整理後可得到如下範例(DM_Management.csv) ```python= Device_Type_Prompt = """ List all Traits associated with each Device Type in Google Home. For example, under the ‘AC_UNIT’ Device Type, list all relevant commands like ‘FanSpeed.' """ ``` ![image](https://hackmd.io/_uploads/rJgc3utZll.png) ## <font size = 5>CCM API使用</font> 1.使用CCM API建立Device Feature: /DF_Structure.csv資料使用CCM API建立DF(IDF和ODF皆需) ```python= # Step1: Converting the DataFrame into a list of dictionaries. [{...}, {...}, ...] df= pd.read_csv("./DF_Structure.csv") df['df_parameter'] = df['df_parameter'].apply(lambda x: ast.literal_eval(x) if pd.notna(x) else []) df = df[df['df_parameter'].apply(lambda x: x != [])] df.index = range(len(df)) dict_rows = df.to_dict(orient="records") # Step2: Create DFs(input & output) for row in dict_rows: row['df_name'] = row['df_name']+"-O" ccm_utils.create_devicefeature(row) for row in dict_rows: row['df_name'] = row['df_name'].rsplit("-", 1)[0]+"-I" row['df_type'] = "input" ccm_utils.create_devicefeature(row) ``` 2.(需批次建立Device Model時使用) 以下流程與DM Management 介面中「使用者手動點選建立DM」會執行的程式碼相同,可參閱VoiceTalk_library/Client/server.py 中的 create_dm_info()與SendData() ```python= dm = pd.read_csv("./DM_Management.csv") dm['Trait'] = dm['Trait'].apply(lambda x: ast.literal_eval(x) if pd.notna(x) else []) def create_dm_info(new_data): traitdf = pd.read_csv("./Trait_Management.csv", dtype={'Trait': 'string', 'DeviceFeature': 'string'}) traitdf['DeviceFeature'] = traitdf['DeviceFeature'].apply(ast.literal_eval) dict1 = new_data.set_index('DM')['Trait'].to_dict() dict2 = traitdf.set_index('Trait')['DeviceFeature'].to_dict() result_dict = {} for key, values in dict1.items(): result_dict[key] = {subkey: dict2[subkey] for subkey in values} dm_name = next(iter(result_dict.keys())) all_values = [value for inner_dict in result_dict.values() for values in inner_dict.values() for value in values] dfs_list = (list(set(all_values))) dm_info = { 'dm_name': dm_name, 'df_list': [] } for dfs in dfs_list: for suffix in ['-O', '-I']: df = dfs + suffix info = ccm_utils.get_devicefeature(df) dm_info['df_list'].append({ 'df_id': info[1]['df_id'], 'df_parameter': list(info[1]['df_parameter']) }) return dm_info for i in range(len(dm)): new_data = dm.iloc[i:i+1] if new_data['Trait']!=[]: # Step1: Preparing data for CCM to create the DM dm_info = create_dm_info(new_data) # Step2: Create the DM status, res = ccm_utils.create_devicemodel(dm_info) ``` # <font size = 6>2. VoiceTalk Management</font> [主程式GitHub](https://github.com/IoTtalk/VoiceTalk_Library/blob/master/VoiceTalk_library/Client/server.py) ## <font size = 5>Function</font> * **get_dflist()** 目的:取得 iottalk 中,同時存在於 IDF 與 ODF 中的 DF 說明:取得vt.iottalk中同時支援input 和output 的DF ```python= # <server.py> def get_dflist(): idf_list = [idf['df_name'] for idf in ccm_utils.get_devicefeature_list()[1]["input"]] odf_list = [odf['df_name'] for odf in ccm_utils.get_devicefeature_list()[1]["output"]] idf_base = [item.replace('-I', '') for item in idf_list if '-I' in item] odf_base = [item.replace('-O', '') for item in odf_list if '-O' in item] DF_LIST = list(set(idf_base) & set(odf_base)) return sorted(DF_LIST) # <server.py> # 更新 Device Feature @app.route('/UpdateDF') def update_df(): DF_LIST = sorted(get_dflist()) return jsonify({"DF_list": DF_LIST}) ``` * **create_dm_info()** 目的:產生「CCM建立DM所需的參數」:dm_info 說明: 1. new_data為一dataframe,欄位為DM, Trait。 2. result_dict為將DM, Trait, DF資料整合的結果。 如: result_dict= {'DOOR': { 'OpenClose': ['OpenClose'], 'LockUnlock':['LockUnlock'] } } 3. 接著按照建立DM所需的格式填入所需資料 ```python= # <server.py> def create_dm_info(new_data): traitdf = pd.read_csv(config.get_trait_management_path("enUS"), dtype={'Trait': 'string', 'DeviceFeature': 'string'}) traitdf['DeviceFeature'] = traitdf['DeviceFeature'].apply(ast.literal_eval) dict1 = new_data.set_index('DM')['Trait'].to_dict() dict2 = traitdf.set_index('Trait')['DeviceFeature'].to_dict() result_dict = {} for key, values in dict1.items(): result_dict[key] = {subkey: dict2[subkey] for subkey in values} dm_name = next(iter(result_dict.keys())) all_values = [value for inner_dict in result_dict.values() for values in inner_dict.values() for value in values] dfs_list = (list(set(all_values))) dm_info = { 'dm_name': dm_name, 'df_list': [] } for dfs in dfs_list: for suffix in ['-O', '-I']: df = dfs + suffix info = ccm_utils.get_devicefeature(df) dm_info['df_list'].append({ 'df_id': info[1]['df_id'], 'df_parameter': list(info[1]['df_parameter']) }) return dm_info ``` ## <font size = 5>Flask route</font> * **/VoiceTalkManagement** VoiceTalk Management網頁 ```python= @app.route('/VoiceTalkManagement',methods=['POST','GET']) def index_iottalk(): return render_template("VT_Management.html") ``` * **/UpdateDF** DF_LIST 是一list ```python= # <server.py> @app.route('/UpdateDF') def update_df(): DF_LIST = sorted(get_dflist()) return jsonify({"DF_list": DF_LIST}) ``` * **/UpdateTrait** Exist_Trait 是一字典:key為Trait名稱、Value為 包含的DF(以list表示) ```python= # <server.py> @app.route('/UpdateTrait') def update_trait(): # Trait Managemnet 的 Trait 內容 df = pd.read_csv(config.get_trait_management_path("enUS"), dtype={'Trait': 'string', 'DeviceFeature': 'string'}) df_sorted = df.sort_values(by='Trait') Exist_Trait = df_sorted.set_index('Trait')['DeviceFeature'].to_dict() return jsonify(Exist_Trait=Exist_Trait) ``` * **/UpdateDM** Exist_DM 是一字典:key為VoiceTalk的DM名稱、Value為包含的Trait(以list表示) IoTtalk_DM 是一字典:key為vt.IoTtalk的DM名稱、Value為空值 (以字典格式以便與其他格式相容),為了在用戶建立DM時,檢查是否與vt.IoTtalk已存在的DM名稱重複 ```python= # <server.py> @app.route('/UpdateDM') def update_DM(): # DM Managemnet的DM df = pd.read_csv(config.get_dm_management_path("enUS"), dtype={'DM': 'string', 'Trait': 'string'}) Exist_DM = df.set_index('DM')['Trait'].to_dict() dm_list = [dm['dm_name'] for dm in ccm_utils.get_devicemodel_list()[1]] # vt.iottalk全部的DM IoTtalk_DM = {key: "" for key in dm_list} return jsonify(Exist_DM=Exist_DM, IoTtalk_DM=IoTtalk_DM) ``` * **/SendData** 1. 若是新增DM: (1) 寫入DM_Management.csv (2) 新資料建立dm_info,用CCM建立DM。 2. 若是新增Trait: (1) 寫入Trait_Management.csv即可 ```python= # <server.py> @app.route('/SendData', methods = ['POST','GET']) def SendData(): try: # DM if request.args.get('trait', None) is None: dm = request.args.get('dm') trait = request.args.getlist('trait[]') # 從查詢參數中獲取 'trait' df = pd.read_csv(config.get_dm_management_path("enUS"), dtype={'DM': 'string', 'Trait': 'string'}) new_data = pd.DataFrame([(dm, trait)], columns =['DM', 'Trait']) df = pd.concat([df, new_data], axis=0) df.to_csv(config.get_dm_management_path("enUS"), index=0) dm_info = create_dm_info(new_data) status, res = ccm_utils.create_devicemodel(dm_info) return jsonify({"dm": dm, "status": status}) # Trait else: trait = request.args.get('trait') device_feature = request.args.getlist('device_feature[]') df = pd.read_csv(config.get_trait_management_path("enUS"), dtype={'Trait': 'string', 'DeviceFeature': 'string'}) new_data = pd.DataFrame([(trait, device_feature)], columns =['Trait', 'DeviceFeature']) df = pd.concat([df, new_data], axis=0) df.to_csv(config.get_trait_management_path("enUS"), index=0) return jsonify({"trait": trait, "status": "true"}) except: return jsonify({"error": "Error"}), 400 # 如果 'trait' 不存在,返回錯誤 ``` Trait_Management.csv 範例 ![image](https://hackmd.io/_uploads/S1__h_tbgl.png) DM_Management.csv 範例 ![image](https://hackmd.io/_uploads/rJgc3utZll.png) ## <font size = 5>HTML</font> [VoiceTalk Management 介面 HTML GitHub](https://github.com/IoTtalk/VoiceTalk_Library/blob/master/VoiceTalk_library/Client/templates/VT_Management.html) * 說明: 1.點擊Device Feature : 打開 vt.iottalk的DF Management頁面,可於此查看之前建立的DF ![DF畫面](https://hackmd.io/_uploads/Syye3e5Zex.png) 2.點擊Trait :下方空白處會顯示Trait Management頁面 ![Trait頁面](https://hackmd.io/_uploads/rJxZhecWee.png) 3.點擊DeviceModel:下方空白處會顯示DM Management頁面 ![DM頁面](https://hackmd.io/_uploads/rkIQ3l5bgg.png) 4.點擊Project : 打開 vt.iottalk的Project頁面 ![Project頁面](https://hackmd.io/_uploads/rJ20nxqWgg.png) * **function showManagement(type)** 點選Banner上的Trait或DeviceModel即可更新頁面。 會根據type='trait'或'dm'更換網頁上的文字內容和列表選項。 (因為兩者的的html模板相同,所以以script部分去切換顯示內容。) ![refreshExist](https://hackmd.io/_uploads/H1XheYtZel.png) * **function refreshExist(currentType)** 1.當點擊Trait Management時:select-box列表會更新Trait內容、leftSelect會更新DF內容 (/UpdateDF) 2.當點擊DM Management時:select-box列表會更新DM內容(/UpdateDM)。 leftSelect會更新Trait內容 (/UpdateTrait) * **document.querySelector(".class-select")** 當在 Class-select 處點選查看某一個 Trait (DM) 時,rightSelect 會顯示該 Trait (DM) 對應的 DF (Trait)。;同時 leftSelect的選單內則不會顯示該 Trait (DM)。 如圖:查看名為Brightness的Trait時,右側顯示的兩個DF,就不會出現在左側的DF清單。 * **function resetSelects(currentType)** 目的:初始化leftSelect列表並保持順序、同時將reightSelect清空 ![addButton](https://hackmd.io/_uploads/BkPuwtYZlx.png) * **function moveSelectedOption()** 目的:DF(Trait)的左右移動 +號為移動到右邊加到最後、-號為移動回左邊 * **leftSelect.addEventListener** **rightSelect.addEventListener** +號和-號的變化控制:當選擇左邊項目時會顯示+號 (如圖點擊左邊的BrightnessAbsolute時,addButton為+號; 點擊左邊的ArmDisarm時,addButton變為-號) * **function setText()** 無論左右邊列表,點擊選中的項目要顯示在showselected處 (上圖綠色框框) * **document.getElementById("saveButton").addEventListener** 點擊 OK 後,系統會提示用戶輸入名稱,系統會檢查: 1.如果用戶是從已存在的 Trait (DM) 進行修改,提示時會顯示該名稱供用戶修改。 2.命名不行與已存在Trait(DM)名字重複,會儲存失敗並顯示其已在使用中。alert(AddName + " is already in use ") 3.建立DM時,會額外檢查名稱是否與 vt.iottalk 的 DM 名稱重複,若重複會儲存失敗並顯示其已在使用中。 (AddName + " is already in use in IoTtalk.") 4.非上述失敗情況,則會進入send_data() * **function send_data(url, data)** 傳送資料給後端儲存