原語(問題)
TypeSafe 的三種問題型別(Choice、Score、Noul)、它們返回的型別化答案、如何在三者之間選擇,以及如何一次提出多個問題。
TypeSafe 的原語是你在程式碼裡組合的小塊、型別化構件。它們成對出現:一個問題定義一個判斷,交給 System One 模型去對一個狀態做出;它的答案就是返回的那個型別化值。你在程式碼裡把這些答案組合起來做決策。共有三種問題型別,各自返回不同形狀的答案。
| 型別 | 回答什麼 | 返回 |
|---|---|---|
| Choice | 這幾個選項裡的哪一個? | choice, probabilities, confidence |
| Score | 哪一檔? | score, legend, probabilities, confidence |
| Noul | 這是真的嗎? | noul(0 到 1) |
你可以只提一個問題,也可以一次提好幾個。請求裡的每個問題看到的都是同一個狀態,各自獨立求值,並在你選定的 ID 下返回一個型別化答案。
每個問題只問一個直覺判斷
System One 模型是為快速、聚焦的判斷而構建的。你要問的,應該是一個懂行的人在拿到合適上下文後一秒鐘內就能做出的判斷。「這條訊息是否表達了緊急?」是個好問題。「分析這條訊息並確定最佳行動方案」則不是。後者需要慢推理,它提示你該把任務拆成小問題,再在程式碼裡組合答案。
如果你想要的判斷依賴多個相互獨立的因素,就分別問每個因素,再用你自己的邏輯組合答案。不要問「給這個創業路演打分」,而是分別問市場規模、技術可行性和差異化,然後在程式碼裡按它們的相對重要性賦予權重。優先順序變了,改權重的值就行,不用重寫提示詞。一次提多個問題演示了具體做法。
定義一個問題
每個問題都有一個 ID、一個 type 和一個 instructions。Choice 和 Score 問題還接受 criteria,它定義 Choice 問題的選項,或 Score 的檔位。Noul 問題接受可選的 criteria,用來澄清「是」和「否」分別指什麼。
- ID。你自己選的鍵名,比如
refund_requested。它用來在響應裡定位這個答案。 type。取值為choice、score或noul之一。instructions。你針對狀態提出的問題。你的評估邏輯就寫在這裡。把它寫成一個清楚、具體的問題,或者一個供模型判斷的陳述句。大多數問題用字串就夠了。它也可以是物件或陣列,把問題放在一個欄位、把問題引用的資料放在其它欄位;見在問題裡使用結構。criteria。可能的答案:Choice 問題是一張選項對映表,Score 是一個有序的檔位列表,Noul 則是對「是」和「否」的可選描述。每種問題型別各自的頁面會講它的形狀。
下面這個問題問的是客戶是否要求退款:
from typesafe_sdk import Noul
questions = {
"refund_requested": Noul(
instructions="Does the customer request a refund?",
),
}
選擇問題型別
挑那個和你需要的答案形狀匹配的型別。
-
Choice 適合答案是一組已知選項之一、且選項之間沒有順序的情況:把工單路由到某個部門、給文件型別分類、識別程式語言。給出完整的選項列表;當列表未必能覆蓋所有輸入時,加上一個
other或none of the above選項。 -
Score 適合答案落在一個譜系上、且你能描述譜系上每個點分別代表什麼的情況:bug 嚴重程度、客戶挫敗感、技能水平。檔位由你定義,模型返回一個落在這條譜系上的位置。
-
Noul 適合一個乾淨的是非題,其中機率本身就是有用的訊號:這條訊息是否包含個人身份資訊、客戶是否在要求退款、簡歷裡是否提到分散式系統。
如果兩種型別看起來都合適,就選那個答案能直接被你的程式碼所用的。在 refund、rebook 和 information 之間做 Choice,直接對應三條程式碼路徑。客戶挫敗感的 Score 對應一個閾值。Noul 對應一個 if。
返回的是什麼
答案也是原語。每種問題型別都返回一個型別化值,你的程式碼可以對它做比較、設閾值、排序、送進後續邏輯,或放進下一次請求的狀態裡(見當一個問題依賴另一個問題)。
| 型別 | 答案欄位 | 怎麼讀 |
|---|---|---|
| Choice | choice, probabilities, confidence |
choice 是選中的選項。probabilities 是在所有選項上的機率分佈。confidence 概括了這條分佈有多尖。 |
| Score | score, legend, probabilities, confidence |
score 是你的檔位上的一處位置,可以落在兩檔之間。legend 按編號重複各檔位。probabilities 是在各檔位上的機率分佈。 |
| Noul | noul |
答案為「是」的機率。接近 1 是很強的「是」,接近 0 是很強的「否」,接近 0.5 則不確定。Noul 沒有單獨的 confidence。 |
這些答案有兩個性質,讓它們可以組合:
- 每個答案都限定在你提供的選項之內。 模型返回的是你給的選項或檔位上的機率分佈,絕不會是這些之外的值。你的程式碼永遠不需要從生成的自然語言裡把值摳出來。
- 每個答案都是獨立的。 一個問題的答案不會成為另一個問題的隱藏上下文。你可以增刪問題,而不影響其它問題的結果。
置信度解釋了 confidence 如何從 probabilities 推匯出來,以及如何用它來決定什麼時候自動執行、什麼時候升級給人工。
引用特定欄位
被求值的內容,也就是狀態,常常是一個含好幾部分的 JSON 物件:一段對話、一條記錄、一條政策。當問題針對其中某一部分時,就在 instructions 裡用點和下標的路徑寫出它的鍵名,記得帶上反引號。這樣模型就知道該判斷狀態裡的哪一部分。
拿狀態頁面裡那段客服對話舉例:
{
"ticket": {
"subject": "Duplicate charge",
"messages": [
{"from": "customer", "text": "I was charged twice for order A-104. Please refund the duplicate."},
{"from": "support", "text": "We are checking the charges."}
]
},
"order": {
"id": "A-104",
"charges": [
{"amount_usd": 49, "status": "captured"},
{"amount_usd": 49, "status": "captured"}
]
},
"refund_policy": "Duplicate charges are eligible for a refund."
}
下面兩個問題用路徑指向客戶訊息、政策和扣款記錄:
questions = {
"refund_requested": {
"type": "noul",
"instructions": "Does `ticket.messages[0].text` request a refund?",
},
"policy_supports_refund": {
"type": "noul",
"instructions": (
"Does `refund_policy` support the refund requested "
"in `ticket.messages[0].text`, given `order.charges`?"
),
},
}
顯式的路徑能讓人看清,結構化狀態的哪些部分該參與每一個判斷。如何組織輸入見狀態。
一次提出多個問題
把用到同一個狀態的問題放在一次請求裡發出去。問題型別可以隨意混用。System One 模型會並行求值一次請求裡的每個問題。增加問題幾乎不改變響應時間,只多花那幾個問題的 token,而它們很便宜。問一個你可能用不上的問題,幾乎等於免費。
下面這次請求一次性完成了客戶訊息分類、緊急度檢查和挫敗感評分:
{
"state": "Our API integration started returning 500 errors on every request about 20 minutes ago, and we can't process any customer orders until this is fixed.",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this",
"criteria": {
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions"
}
},
"is_urgent": {
"type": "noul",
"instructions": "The message conveys urgency or time-sensitivity"
},
"frustration": {
"type": "score",
"instructions": "How frustrated the customer appears",
"criteria": [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language"
]
}
}
}我們的客戶端 SDK提供型別化的問題和答案。在 Python 裡,把一個裝著 Choice、Noul 和 Score 物件的 questions 字典傳給 client.system_one(...)。下面這次請求把一張工單和一份退款政策發一次,就能拿到每個問題的型別化答案:
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
state = {
"ticket_message": "My flight was cancelled. Can I get a refund?",
"refund_policy": "Cancelled flights are eligible for a full refund.",
}
with TypeSafeClient() as client:
response = client.system_one(
state=state,
questions={
"refund_requested": Noul(
instructions="Does `ticket_message` request a refund?",
),
"request_type": Choice(
instructions="What is the main request in `ticket_message`?",
criteria={
"refund": "The customer wants money returned.",
"rebooking": "The customer wants a replacement flight.",
"information": "The customer is asking for information only.",
},
),
"frustration": Score(
instructions="How frustrated does the customer appear in `ticket_message`?",
criteria=[
"Calm and neutral.",
"Concerned but civil.",
"Very angry or using strong language.",
],
),
},
)
print(response.answers["refund_requested"].noul)
print(response.answers["request_type"].choice)
print(response.answers["frustration"].score)
安裝方式和各語言的用法見客戶端 SDK。
提一些推測性的問題
把程式碼可能需要的每個問題都問出來,包括那些只對部分輸入才有意義的,再讓程式碼決定用哪些答案。如果一張工單最後發現不是 bug 報告,忽略嚴重程度那個答案就是了。我們把這叫做推測式扇出模式。並行提問 cookbook展示了把 13 個問題並進一次呼叫,比 13 次單獨呼叫便宜 11.5 倍、快 9.6 倍,而答案沒有任何變化。
把複合判斷拆成幾個問題
一個依賴好幾件事的判斷,最好拆成每件事一個問題。在程式碼裡組合這些答案,並按相對重要性給每個答案一個權重。權重由你定。當組合結果和你們團隊的判斷不一致時,就在程式碼裡改權重再跑一遍。增加問題幾乎不改變響應時間,因為它們在同一次請求裡並行執行。拆分只多花幾個問題的 token。
比如,工單優先順序可以由三個 Score 問題搭出來:bug 有多嚴重、客戶有多不滿、報告給了工程師多少可用資訊。Score 那一頁在把複合判斷拆成幾個 Score一節裡完整演示了這次請求,以及把答案歸一化並加權的那段程式碼。這個技巧叫做組合評分模式。
當一個問題依賴另一個問題
同一次請求裡的問題是相互獨立的:一個答案不會成為另一個問題的上下文。如果後面的判斷依賴前面的答案,那就在程式碼裡發第二次請求。只有當你的程式碼拿不到第一個答案就無法構造第二次請求時,依賴才真正存在:它需要這個答案去為狀態取更多資料、決定狀態由什麼構成,或挑選下一個問題的選項。否則,就把問題一起問,在程式碼裡組合它們的答案。
兩次請求是例外,不是慣例。如果第二次請求裡的問題本來可以對原始狀態提出,那就放在第一次請求裡問,再讓程式碼忽略用不到的那些。有三個 cookbook 出於真正必要的理由才發第二次請求。技能建議在一次請求裡給 182 個技能排序,然後取回前三名的全文,再拿這份更好的證據重新判斷它們。結構還原先問每個換行是否切斷了句子,再根據這些答案把行合併成塊,然後給這些塊分類 —— 而這些塊在第一次請求給出答案之前根本不存在。層級分類用每個 Choice 答案來決定下一次請求提供哪些選項。
如何把工作流拆成一個個聚焦的判斷,見如何用 TypeSafe 構建。
後續步驟
Choice
從固定列表裡挑一個選項。
Score
按有序檔位給狀態評分。
Noul
得到某個陳述為真的機率。
想看它們如何組合成系統架構,去架構模式。