script (⏱,💰)

script (⏱,💰)

MyShell Pro Config 進階技巧分享

本文面向已了解 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 ProConfig 鏈接

Think Tank

起初,我僅按照教程創建了 Think Tank 的 Pro Config 版來通過考核,但未理解 Pro Config 的全部功能。最近新增了推薦話題功能來促進用戶與 AI 的互動。在開發新功能中,我對 Pro Config 有了更深刻的認識。以下總結出了一些官方教程未詳細說明的實用技巧。

變量#

Pro Config 的變量分為全局變量和局部變量,具體在官方教程的 expressions and variables 章節中有具體闡述。

全局變量用 context 讀寫。適合存各種類型的數據:

  1. string:字符串,可用於保存文本類型的輸出,或將 JSON 對象通過 JSON.stringify 序列化後存儲。
  2. prompt: 提示詞,將 system_prompt 存入 context,將需傳遞的變量全部置於 user_prompt。
  3. list:列表,非字符串類型應使用 {{}} 進行包裹。
  4. number: 數字,同樣需要用 {{}}包裹
  5. bool:布爾值,同樣需要用 {{}}包裹
  6. null: 可空值
  7. 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 將其置於按鈕描述裡,需鼠標懸停方可查看。

Aldea

如果 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)}}"
  },
……

輸出結果:
result

更詳細用法可參考 官方 ProConfig Tutorial 中的示例。

記憶(memory)#

使用以下代碼把最新聊天消息添加到 memory 中,並將更新後的 memory 通過 LLMModulememory 參數傳遞給 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"
        }
      }
    }
  }

運行第一輪後的結果如下:

memory

然後分別輸入 next is 2third is 8final 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}]}}"

其他內存管理策略包括:

  1. 只保留最近的幾條對話記錄,例如 ...context.memory.slice(-2) 只會寫入最新 2 條歷史記憶。
  2. 根據主題將記憶分類存儲。社區優秀創作者 ika 在他的遊戲中用 "yuna_memory":"{{[]}}","yuna_today_memory":"{{[]}}", 來存儲角色 yuna 全局和當日的記憶

總結#

本文介紹了使用 MyShell 編寫 Pro Config 的一些進階技巧,包括變量、JSON 生成和 memory 。

如果想更多了解 MyShell,請查看作者整理的 awesome-myshell

如果對 Pro Config 感興趣,想成為 AI 創作者,記得報名 Learning Hub,點擊此處跳轉報名

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。