文件導航

原語(問題)

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,而它們很便宜。問一個你可能用不上的問題,幾乎等於免費。

下面這次請求一次性完成了客戶訊息分類、緊急度檢查和挫敗感評分:

request
{
  "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

得到某個陳述為真的機率。

想看它們如何組合成系統架構,去架構模式。