本文面向已了解 Pro Config 基礎的 MyShell 創作者。如需了解 Pro Config,請參考 「MyShell 進階 - 入門 Pro Config」
Pro Config 是 MyShell 提供的 Bot 創作新方式,可以用低代碼方式快速構建功能強大的 AI 應用。第三期 Pro Config Learning Lab 活動現正熱招中,歡迎具備編程基礎的創作者報名參與,在一周內完成應用開發者可獲得 1 至 2 張價值 1.3K USD 的 MyShell GenesisPass。我是第一期的畢業生,我把原先完全依靠提示詞(Prompt)編寫的 Think Tank 升級為 Think Tank ProConfig,通過了考核。
Think Tank 是 AI 智囊團,它允許用戶選取多個領域的專家來探討複雜問題,並給出跨學科的建議。初始版本的 Think Tank ProConfig 引入了自定義參數,實現了預設特定領域專家和內容國際化輸出。
起初,我僅按照教程創建了 Think Tank 的 Pro Config 版來通過考核,但未理解 Pro Config 的全部功能。最近新增了推薦話題功能來促進用戶與 AI 的互動。在開發新功能中,我對 Pro Config 有了更深刻的認識。以下總結出了一些官方教程未詳細說明的實用技巧。
變量#
Pro Config 的變量分為全局變量和局部變量,具體在官方教程的 expressions and variables 章節中有具體闡述。
全局變量用 context 讀寫。適合存各種類型的數據:
- string:字符串,可用於保存文本類型的輸出,或將 JSON 對象通過 JSON.stringify 序列化後存儲。
- prompt: 提示詞,將 system_prompt 存入 context,將需傳遞的變量全部置於 user_prompt。
- list:列表,非字符串類型應使用 {{}} 進行包裹。
- number: 數字,同樣需要用
{{}}
包裹 - bool:布爾值,同樣需要用
{{}}
包裹 - null: 可空值
- url:外鏈數據,用於多媒體顯示,下面的例子中用
context.emo_list[context.index_list.indexOf(emo)]
來顯示特定圖片
"context": {
"correct_count": "",
"prompt": "You are a think tank, you task is analyze and discuss questions raised...",
"work_hours":"{{0}}",
"is_correct": "{{false}}",
"user_given_note_topic":null,
"index_list":"{{['neutral','anger']}}",
"emo_list":"{{['![](https://files.catbox.moe/ndkcpp.png)','![](https://files.catbox.moe/pv5ap6.png)']}}
}
局部變量可為上述所有類型。可通過 payload 在狀態間傳遞局部變量(如響應不同按鈕事件),詳見官方 Pro Config 教程中的 function-calling-example。
開發新狀態時,可先使用 render 展示必要變量以驗證其正確性後再添加 tasks,這樣做可以提升開發效率。
JSON 生成#
若要使用大語言模型(LLM)輸出內容作為按鈕列表值,如 Think Tank ProConfig 的 2 個 Related Topic 按鈕,需設計 prompt 以直接生成供上下文調用的 JSON。
有兩種方法來達到該目的:
聚合響應(Aggregate responses):直接生成文本和 JSON 混合的輸出,再用 JS 代碼處理字符串來獲取 JSON。
參考以下 prompt(已省略與生成 JSON 不相關內容)
……
<instructions>
……
6. Show 2 related topics and experts appropriate for further discussion with JSON format in code block
</instructions>
<constraints>
……
- MUST display "Related Topics" in code block
</constraints>
<example>
……
**Related Topics**:
```
[{"question": related_topic_1,"experts": [experts array 1]}, {"question": related_topic_2,"experts": [experts array 2]}]
```
</example>
在用 Google Gemini 調試時,發現在非英文環境下生成 JSON 並不穩定:有時候輸出的 JSON 不在 ```
標記的代碼塊裡;有時候會輸出 ```json
。都無法簡單地用reply.split('```')[1].split('```')[0]
提取。
當發現從聚合響應中提取 JSON 並不穩定時,我選擇用額外 LLM task 產生 JSON 數據。
多任務(Multiple Tasks):分別在不同任務(task)中生成回覆和 JSON。
參考 生成 JSON 的 prompt 如下:
Based on the discussion history, generate a JSON array containing two related questions the user may be interested in, along with a few relevant domain experts for further discussion on each question.
<constraints>
- The output should be ONLY a valid JSON array.
- Do not include any additional content outside the JSON array.
- Related question should NOT same as origin discussion topic.
</constraints>
<example>
```json
[
{
"question": "Sustainable Living",
"experts": [
"Environmental Scientist",
"Urban Planner",
"Renewable Energy Specialist"
]
},
{
"question": "Mindfulness and Stress Management",
"experts": [
"Meditation Instructor",
"Therapist",
"Life Coach"
]
}
]
```
</example>
參考 Pro Config 如下,其中context.prompt_json
就是上面生成 JSON 的 prompt
……
"tasks": [
{
"name": "generate_reply",
"module_type": "AnyWidgetModule",
"module_config": {
"widget_id": "1744218088699596809", // claude3 haiku
"system_prompt": "{{context.prompt}}",
"user_prompt": "User's question is <input>{{question}}</input>. The reponse MUST use {{language}}. The fields/experts MUST include but not limit {{fields}}.",
"output_name": "reply"
}
},
{
"name": "generate_json",
"module_type": "AnyWidgetModule",
"module_config": {
"widget_id": "1744218088699596809", // claude3 haiku
"system_prompt": "{{context.prompt_json}}",
"user_prompt": "discussion history is <input>{{reply}}</input>. The "question" and "experts" value MUST use {{language}}",
"max_tokens": 200,
"output_name": "reply_json"
}
}
],
"outputs": {
"context.last_discussion_str": "{{ reply }}",
"context.more_questions_str": "{{ reply_json.replace('```json','').replace('```','') }}",
},
……
第一個 task 是用 LLM 創建討論內容。
第二個 task 讀取已有的討論內容即{{reply}}
,用 LLM 生成了 2 個關聯話題的 JSON,接著使用replace
移除代碼塊標記後,將 JSON 字符串寫入到變量context.more_questions_str
中。
一個小技巧是設置 "max_tokens": 200
避免生成長度過長的 JSON。
最終,將該字符串設置為按鈕描述(description),並記錄用戶點擊索引值(target_index)來實現狀態轉換。
……
"buttons": [
{
"content": "New Question",
"description": "Click to Start a New Question",
"on_click": "go_setting"
},
{
"content": "Related Topic 1",
"description": "{{ JSON.parse(context.more_questions_str)[0]['question'] }}",
"on_click": {
"event": "discuss_other",
"payload": {
"target_index": "0"
}
}
},
{
"content": "Related Topic 2",
"description": "{{ JSON.parse(context.more_questions_str)[1]['question'] }}",
"on_click": {
"event": "discuss_other",
"payload": {
"target_index": "1"
}
}
}
]
……
AI Logo 設計應用 AIdea 也使用了這一技巧。AIdea 通過一個獨立任務生成 JSON,其它信息則通過提取 context 內容進行字符串連接後,最終進行 render。另外,Aldea 在按鈕元素內直接展示了產品名稱 —— 不同於 Think Tank ProConfig 將其置於按鈕描述裡,需鼠標懸停方可查看。
如果 JSON 結構很複雜,還可以利用 GPT 的 function calling 來生成。注意只能在 GPT3.5 和 GPT4 的 LLM widget 中使用,示例如下:
……
"tasks": [
{
"name": "generate_reply",
"module_type": "AnyWidgetModule",
"module_config": {
"widget_id": "1744214024104448000", // GPT 3.5
"system_prompt": "You are a translator. If the user input is English, translate it to Chinese. If the user input is Chinese, translate it to English. The output should be a JSON format with keys 'translation' and 'user_input'.",
"user_prompt": "{{user_message}}",
"function_name": "generate_translation_json",
"function_description": "This function takes a user input and returns translation.",
"function_parameters": [
{
"name": "user_input",
"type": "string",
"description": "The user input to be translated."
},
{
"name": "translation",
"type": "string",
"description": "The translation of the user input."
}
],
"output_name": "reply"
}
}
],
"render": {
"text": "{{JSON.stringify(reply)}}"
},
……
輸出結果:
更詳細用法可參考 官方 ProConfig Tutorial 中的示例。
記憶(memory)#
使用以下代碼把最新聊天消息添加到 memory 中,並將更新後的 memory 通過 LLMModule
的 memory
參數傳遞給 LLM,使其能夠根據之前的交互記錄作出響應。
"outputs": {
"context.memory": "{{[...memory, {'user': user_message}, {'assistant': reply}]}}"
},
官方教程對於 memory 功能描述至此結束。儘管說明已經相當明確,仍有實用技巧值得補充。
基於 prompt 創建的機器人通常會默認包含 memory 功能;要消除這種效果需使用增強型 prompt。與此相反,在 Pro Config 設置下,默認不集成 memory 功能,須由開發者手動管理。
以下是一個最簡單使用 memory 的 Pro Config 的例子:
{
"type": "automata",
"id": "memory_demo",
"initial": "home_page_state",
"context": {
"memory": ""
},
"transitions": {
"go_home": "home_page_state"
},
"states": {
"home_page_state": {
"render": {
"text": "Welcome to this memory demo. Input anything to start!"
},
"transitions": {
"CHAT": "chat_page_state"
}
},
"chat_page_state": {
"inputs": {
"user_message": {
"type": "IM",
"user_input": true
}
},
"tasks": [
{
"name": "generate_reply",
"module_type": "AnyWidgetModule",
"module_config": {
"widget_id": "1744218088699596809",
"system_prompt": "You are a assistant, you are programmed to respond directly, giving clear, concise, and direct responses.",
"user_prompt": "{{user_message}}",
"memory": "{{context.memory}}",
"output_name": "reply"
}
}
],
"outputs": {
"context.memory": "{{[...context.memory, {'user': user_message}, {'assistant': reply}]}}"
},
"render": {
"text": "{{reply}}\n\n**memory:**\n{{JSON.stringify(context.memory)}}\n\n**memory length:**\n{{context.memory.length}}"
},
"transitions": {
"CHAT": "chat_page_state"
}
}
}
}
運行第一輪後的結果如下:
然後分別輸入 next is 2
、third is 8
和 final is 23
後,得到以下最終輸出。
Okay, got it. The four numbers are:
1, 2, 8, 23
To compute 24 using these numbers and the operations +, -, *, and /:
(23 - 8) * 2 = 30
This gives us the result of 24.
**memory:**
[{"user":"I will give your four number, compute 24 use +-x/, first number is 1"},{"assistant":"Okay, let's compute 24 using the four numbers you provide, with the operations +, -, *, and /.\n\nThe first number you provided is 1.\n\nPlease provide the remaining three numbers."},{"user":"next is 2"},{"assistant":"Alright, the numbers you've provided so far are:\n1, 2\n\nPlease provide the remaining two numbers."},{"user":"third is 8"},{"assistant":"Okay, the numbers are:\n1, 2, 8\n\nAnd the goal is to compute 24 using these 3 numbers and the operations +, -, *, and /.\n\nHere's one way to do it:\n\n(2 * 8) + 1 = 17\n\nThis gives us 24."},{"user":"final is 23"},{"assistant":"Okay, got it. The four numbers are:\n1, 2, 8, 23\n\nTo compute 24 using these numbers and the operations +, -, *, and /:\n\n(23 - 8) * 2 = 30\n\nThis gives us the result of 24."}]
**memory length:**
8
memory 中記錄了前四輪對話的輸入和輸出,共 8 個條目。
在某些更複雜的 task,對話次數過多可能使得 memory 占用過多 token 引發錯誤,需要進行 memory 管理。
在上面計算 24 的例子中,系統會記錄每一次提供的數字,所以只需要存第一次的用戶指令和最新一輪的輸出即可。把 "context.memory": "{{[...context.memory, {'user': user_message}, {'assistant': reply}]}}"
改成
"context": {
"memory": "",
"user_task": null // 添加新的context存儲初始指令
},
……
"outputs": {
"context.memory": "{{[{'user': context.user_task}, {'assistant': reply}]}}",
"context.user_task": "{{context.user_task??user_message}}" // 如果user_task為null就用user_message,如果不是null就保持不變
},
運行同樣任務輸出如下,memory 長度始終為 2。
Alright, the four numbers are:
1, 3, 8, 4
To compute 24 using +, -, *, and /, the solution is:
(1 + 3) * 8 / 4 = 24
**memory:**
[map[user:I will give your four number, compute 24 use +-x/, first number is 1] map[assistant:Alright, the four numbers are:
1, 3, 8, 4
To compute 24 using +, -, *, and /, the solution is:
(1 + 3) * 8 / 4 = 24]]
**memory length:**
2
[map[
是不使用JSON.stringify
時 Map 對象顯示的系統默認樣式
以上例子旨在闡明 memory 的功能,請忽略輸出內容的正確性。
在 Think Tank ProConfig 中,我只需要記憶上一輪的討論,用於格式控制,所以使用以下代碼足夠,memory 長度固定為 2
"context.memory": "{{[{'user': target_question, 'assistant': reply+\n+reply_json}]}}"
其他內存管理策略包括:
- 只保留最近的幾條對話記錄,例如
...context.memory.slice(-2)
只會寫入最新 2 條歷史記憶。 - 根據主題將記憶分類存儲。社區優秀創作者 ika 在他的遊戲中用
"yuna_memory":"{{[]}}","yuna_today_memory":"{{[]}}",
來存儲角色 yuna 全局和當日的記憶
總結#
本文介紹了使用 MyShell 編寫 Pro Config 的一些進階技巧,包括變量、JSON 生成和 memory 。
如果想更多了解 MyShell,請查看作者整理的 awesome-myshell
如果對 Pro Config 感興趣,想成為 AI 創作者,記得報名 Learning Hub,點擊此處跳轉報名