Guidance 是微軟推出的一款工具,被描述為「用於控制大型語言模型的指導語言」。它的主要功能是幫助用戶控制大型語言模型(LLM)的輸出,使之更容易遵循「指令提示」(Prompt),以提升語言模型對於複雜指令的遵循度和回應的準確性。這對於需要精確控制輸出的應用場景尤為重要。

簡單來說,你可以把它想成指令提示的模板語言,增加 LLM 跟應用程式的可交互性。目前最常見的指令提示編寫由人工手動完成。考慮下面幾個場景,人工編寫的指令提示便無法勝任:

小林,我之前設計了一個AI導遊的對話劇本,主要是介紹台北故宮博物院。你能幫我把劇本改成介紹高雄佛光山嗎?只需把提到台北故宮的部分換成佛光山相關內容就行了";

小王,我們需要一些關於AI美食評論家在台灣夜市品嘗小吃的多輪對話樣本,你看看怎麼利用 GPT-4 生成一些,然後按照 JSON、Excel 格式整理好給我";

小趙,我現在這個AI旅遊助手的對話設定有點複雜,我想在對話中加入一些關於台灣高鐵的API調用,邏輯也比較複雜,你看看有沒有現成的方法能簡化這個過程"。

這邊我們以一個訂單產生器當範例介紹。假定我們的需求是生成 PizzaNoddlesPho 三種訂單,並且以 JSON 格式輸出。

首先定義合法的餐點 valid_dish

valid_dish = ["Pizza", "Noodles", "Pho"]

接下來,定義指令提示 order_maker,這邊可以看到指令提示很類似 HandlebarsJs 模板中語言,其中 {{...}}为基本语法,裡頭的字串可用於調用變數、函數,使用控制流程等等,所以可以類比 HandlebarsJs 語法中的 {{...}}

order_maker = guidance("""The following is a order in JSON format.
```json
{
    "name": "{{name}}",
    "age": {{gen 'age' pattern='[0-9]+' stop=','}},
    "delivery": "{{#select 'delivery'}}Yes{{or}}No{{/select}}",
    "order": "{{select 'order' options=valid_dish}}",
    "amount": {{gen 'amount' pattern='[0-9]+' stop=','}}
}```""")

{{gen 'age' pattern='[0-9]+' stop=','} 用來調用 LLM 產生年紀,並將結果嵌入回模板中。{{#select 'delivery'}}Yes{{or}}No{{/select}}{{select 'order' options=valid_dish}} 為選擇輸出語法。在本範例中,delivery 的輸出是 Yes 或是 No,而 order 的輸出則是 valid_dish 其中之一。

最後建立出來的 order_maker 便能拿來在應用程式中重複使用。

order_maker(
    name="Alex",
    valid_dish=valid_dish
)

輸出的 JSON 檔案如下:

{
    "name": "Alex",
    "age": 25,
    "delivery": "Yes",
    "order": "Noodles",
    "amount": 10
}

根據上述,我們可看出 guidance 是一個 High-order function, 用來建立一個小型的 大型語言模型應用。別是 String -> Params -> Output。本例中的型別是 String -> {name: String, valid_dish: String[] -> JSON

除了基本的變數置換,guidance 也可以在模板中呼叫外部定義的函式,透過 tools 傳遞 function 物件進去,讓 guidance 使用。下面這個範例,便讓指令提示可以呼叫,外面透過 @guidance 定義的 addsubtractmultipledivide 做四則運算。

@guidance
def add(lm, input1, input2):
    lm += f' = {int(input1) + int(input2)}'
    return lm
@guidance
def subtract(lm, input1, input2):
    lm += f' = {int(input1) - int(input2)}'
    return lm
@guidance
def multiply(lm, input1, input2):
    lm += f' = {float(input1) * float(input2)}'
    return lm
@guidance
def divide(lm, input1, input2):
    lm += f' = {float(input1) / float(input2)}'
    return lm

lm = llama2 + '''\
1 + 1 = add(1, 1) = 2
2 - 3 = subtract(2, 3) = -1
'''
lm + gen(max_tokens=15, tools=[add, subtract, multiply, divide])

總而言之,guidenace 可以混用不同的大型語言模型,處理 token 邊界,暫存計算結果,以更細膩的方式控制輸出格式。我自己玩起來,跟用 QuickCheckfaker.jsHypothesis的思路類似,以一種特定的方式去控制生成結果。差別在前者是可預期的亂數,而大型語言模型是不可預期的亂數。

除此之外,guidance 也可以跟 Langchain 一起使用,見 Learn Langchain with local LLMsMake a simple agent with Guidance and local LLMsGuidanceLangChain 都旨在幫助用戶更有效地利用和控制大型語言模型,但它們各自關注的重點和應用場景有所不同。

Guidance 主要專注於如何更有效地控制語言模型的生成過程,提供了一種更自然的方式來組織生成、提示和邏輯控制的流程。這對於那些需要在一個連續流程中交替使用生成、提示和邏輯控制的場景特別適用,例如基於聊天的應用或需要生成具有特定結構的文本的應用。

相比之下,LangChain 則提供了一個更全面的框架,它涵蓋了從數據獲取、處理到模型調用,再到結果呈現的整個流程。它為基於大型語言模型的應用開發和部署提供了一套完整的工具和介面。可以說,Langchain 負責的範圍是膠粘層,而 guidance 是對大型語言模型做更細膩的直接控制。

因為 guidance 每一個對大型語言模型中的調用函數都是 Immutable。很自然的,你會滿想把他包成 Flow-based Programming 框架,方便在不同的第三方服務,做資料的轉換、整合、儲存,讓人用圖形化的方式,拉一拉做一個基於 service 的 Agent。

去年花了幾天,做了一個小雛形,概念像這樣:

from langchain.llms import OpenAPI
from commandchain.units import IOUnit, LLMUnit
from commandchain.chains import Chain
from commandchain.io import HTTPRequest

# Instantiate the OpenAI language model
llm = OpenAPI(openai_api_key="...", temperature=0.9)

# Define the source unit which fetches weather data
source = IOUnit(action=HTTPRequest(
  'GET',
  'http://api.weatherapi.com/v1/current.json',
  params={'key': '...', 'q': 'New York'}))

# Define the worker unit that uses LLM to interpret
# the weather data and generate a friendly message
worker = LLMUnit(
  llm=llm,
  prompt=lambda data: f"Translate the weather data '{data}' into a friendly message...")

# Define the destination unit that prints the friendly
# weather message to console
dest = IOUnit(action=lambda data: print(data))

# Create a Chain instance and connect the units
chain = Chain()
chain.connect(source, 'success', worker, 'in')
chain.connect(worker, 'success', dest, 'in')

# Serve the chain on a specified port
chain.serve(port=3000)

source 是一個 IOUnit,用於從天氣API獲取數據。這裡使用了 HTTPRequest 來發送 GET 請求,獲取紐約的當前天氣數據。worker 是一個 LLMUnit,它使用語言模型來處理從 source 獲取的天氣數據。這裡定義了一個提示,指示語言模型將天氣數據轉換為友好的訊息。然後使用 Chain 來創建一個處理鏈,並通過 connect 方法將 sourceworkerdest 這三個單元按順序連接起來。最後,使用 chain.serve(port=3000) 啟動服務。

然後就可以使用這個 API 了。

curl http://localhost:3000 -q Taipei

實作中遇到的問題是,業務邏輯要放在指令提示層,還是放在黏合層?選擇困難,腦袋會打架。不太確定業界是否有使用 guiance,但可做為未來設計大型模型語言應用的參考。

延伸閱讀