<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.'
"""
```

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.'
"""
```

## <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 範例

DM_Management.csv 範例

## <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

2.點擊Trait :下方空白處會顯示Trait Management頁面

3.點擊DeviceModel:下方空白處會顯示DM Management頁面

4.點擊Project : 打開 vt.iottalk的Project頁面

* **function showManagement(type)**
點選Banner上的Trait或DeviceModel即可更新頁面。
會根據type='trait'或'dm'更換網頁上的文字內容和列表選項。
(因為兩者的的html模板相同,所以以script部分去切換顯示內容。)

* **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清空

* **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)**
傳送資料給後端儲存