スキーマ駆動の意思決定
スキーマ駆動の意思決定
JSON スキーマ、または pydantic モデルを Laya の質問に変換し、キャリブレーション済みの信頼度が 付いた型付きの値を受け取ります。これが Laya を構造化出力エンジンにする橋です。欲しい形を 記述すれば、Laya が 1 回のフォワードパスで答えます。
import laya
schema = {
"type": "object",
"properties": {
"department": {"type": "string", "enum": ["billing", "support", "sales"],
"description": "Which team should handle this?"},
"urgency": {"type": "integer", "minimum": 0, "maximum": 2},
"needs_human": {"type": "boolean"},
},
}
agent = laya.load("convaiinnovations/laya")
values = agent.decide("I was charged twice, refund me.", schema=schema)
# {"department": "billing", "urgency": 2, "needs_human": True}
pydantic を使う場合(laya[structured] をインストール):
from typing import Literal
from pydantic import BaseModel
class Ticket(BaseModel):
department: Literal["billing", "support", "sales"]
urgency: Literal[0, 1, 2]
needs_human: bool
ticket = agent.decide("I was charged twice, refund me.", schema=Ticket)
対応する部分集合
トップレベルは properties を持つオブジェクトでなければなりません。各プロパティが 1 つの
Laya の質問になります。
以下の各行は実際のスキーマです。tests/test_structured_docs.py が最初の列をコンパイルし、
コンパイラが実際に生成する質問を検証するので、この表がコードからずれることはありません。
セルは、それ自体が 1 つのプロパティスキーマであるか、エントリポイントの呼び出しです。
| JSON スキーマ | Laya の質問 | 返される値 |
|---|---|---|
{"enum": ["billing", "support"]} |
choice |
選ばれた値(元の型のまま) |
{"const": "billing"} |
choice |
その 1 つの値 |
{"type": "boolean"} |
noul |
true / false |
{"type": "integer", "minimum": 0, "maximum": 5} |
score |
確率が最も高いレベルを整数で |
{"type": "number", "minimum": 0, "maximum": 5} |
score |
そのレベルを整数で |
{"type": "string", "enum": ["low", "high"], "description": "How urgent?"} |
choice |
How urgent? が質問の文言になります |
{"anyOf": [{"enum": ["x", "y"]}, {"type": "null"}]} |
choice |
通常の enum の行と同じ。答えがない場合はキーが出力に含まれません |
{"oneOf": [{"type": "boolean"}, {"type": "null"}]} |
noul |
通常の boolean の行と同じ |
{"type": ["integer", "null"], "minimum": 1, "maximum": 3} |
score |
通常の上限付き整数の行と同じ |
Literal[...] と Optional[...] は、enum と anyOf の各行に対応する pydantic での書き方です。
questions_from_pydantic がそれらをこれらの形に描画し、同じ行が当てはまります。
title は読み取られません。pydantic は要求の有無にかかわらず model_json_schema() の
すべてのフィールドにこれを付けるうえ、プロパティ単位の名前では、1 つの質問が組み立てられる
元になる選択肢ごとの choice にラベルを付けられません。したがって文言を操作するレバーは
description です —— 後述の内部でどう対応するかを参照してください。
射影は正確です。enum: [1, 2, 3] は 2 を返し、"2" は返しません。上限付き整数は minimum と
maximum の間のレベルを返します。真偽値は noul >= 0.5 です。
拒否
固定の選択肢集合から答えられないスキーマは、laya.structured.SchemaError(ValueError)を
送出し、正確なパスを名指しします。各行も実行され、フィールド名は name です:
| プロパティスキーマ | メッセージ |
|---|---|
{"type": "string"} |
properties.name: a free string cannot be a fixed option set; use 'enum' or a boolean |
{"type": "array", "items": {"type": "string"}} |
properties.name: arrays are not supported; ask one field per element |
{"type": "object", "properties": {"inner": {"type": "boolean"}}} |
properties.name: nested objects are not supported; flatten the schema |
{"$ref": "#/definitions/node"} |
properties.name: $ref/recursion is not supported; flatten the schema |
{"enum": [1, "1"]} |
properties.name: enum values produce duplicate choice labels |
{"enum": []} |
properties.name: 'enum' must not be empty |
{"type": "number"} |
properties.name: a numeric field needs integer 'minimum' and 'maximum' to become a score |
{"type": "integer", "minimum": 5, "maximum": 2} |
properties.name: 'maximum' 2 is below 'minimum' 5 |
{"type": "integer", "minimum": 0, "maximum": 10} |
properties.name: 11 levels exceeds MAX_SCORE_LEVELS=10; narrow the range or use an enum |
{"anyOf": [{"type": "string"}, {"type": "integer"}]} |
properties.name: only 'Optional[...]' unions (one non-null branch) are supported, got 2 |
{"type": ["string", "integer"]} |
properties.name: 'type' has multiple non-null types; unions are not supported |
{"format": "date"} |
properties.name: unsupported schema {'format': 'date'} |
"boolean" |
properties.name: property must be an object, got str |
エントリポイント自身はこれらを拒否します:
| 呼び出し | メッセージ |
|---|---|
plan_from_json_schema("not a schema") |
expected a JSON schema object, got str |
plan_from_json_schema({"type": "object"}) |
the top level must be an object with 'properties' |
plan_from_json_schema({"type": "object", "properties": {}}) |
'properties' must be a non-empty object |
plan_from_json_schema({"type": "object", "properties": {"p%d" % i: {"type": "boolean"} for i in range(33)}}) |
33 properties exceeds MAX_PROPERTIES=32 |
plan_from_json_schema({"type": "object", "properties": {"name": {"enum": ["v%d" % i for i in range(33)]}}}) |
properties.name: 33 options exceeds MAX_OPTIONS=32 |
decide(None, "I was charged twice.", schema=42) |
expected a JSON schema dict or a pydantic model, got int |
制限:MAX_PROPERTIES = 32、MAX_OPTIONS = 32、MAX_SCORE_LEVELS = 10。
API
| 関数 | 用途 |
|---|---|
laya.decide(runner, state, schema=..., *, questions=..., return_details=..., min_confidence=..., **predict_kwargs) |
自由関数で、Agent と Router の両方に使えます |
agent.decide(state, schema=..., ...) / router.decide(state, schema=..., ...) |
便利メソッド |
laya.decide_batch(runner, states, schema=..., ...) / agent.decide_batch(...) / router.decide_batch(...) |
多数の状態に対して同じことをする、1 回のバッチ呼び出し |
questions_from_json_schema(schema) |
スキーマから Laya の質問へ |
questions_from_pydantic(model) |
pydantic モデルから質問へ(pydantic が必要) |
answers_to_json(answers, schema) |
生の答えをスキーマの値へ射影 |
answer_to_pydantic(model, answers) |
生の答えを pydantic インスタンスへ射影 |
plan_from_json_schema(schema) |
検証済みのフィールド計画(上級) |
schema か questions のどちらか一方だけを渡します。questions を渡すと、decide は射影せず
に生の答えを返します。余分なキーワード引数は predict へ転送されるので、フック、model=、
task=、token 予算はすべて機能します:
router.decide(state, schema=Ticket, model="multilingual", hooks=[Metrics()])
多数の状態をスコアリングする
decide_batch はスループット重視の形です。スキーマは 1 回だけ計画され、その質問は
predict_batch を通じてすべての状態にわたり実行されるので、状態は呼び出しごとではなく
フォワードパスを共有します。結果は入力順に返り、decide が射影するのとまったく同じに射影
されます。return_details=True は状態ごとに 1 つの DecisionResult を返します:
values = agent.decide_batch(ticket_texts, schema=Ticket) # values[i] matches ticket_texts[i]
results = router.decide_batch(states, schema=Ticket, return_details=True, batch_size=64)
Router では各状態は依然として個別にルーティングされるので、1 回の呼び出しが複数の
チェックポイントにまたがることがあります。キーワード引数は predict_batch に届くので、
batch_size=、model=、フックは decide と同様に機能します。Agent、ONNXAgent、Router
はいずれもこれを持ちます。predict_batch を持たない runner は、黙ってループにフォールバック
するのではなく TypeError を送出します —— その場合は状態ごとに decide を呼んでください。
バッチ処理は predict_batch と同じように境界付近の argmax をずらしえます。README には両方の
デバイスで実測された高速化が記録されています。
信頼度と確率
既定では decide は値だけを返します。return_details=True を渡すと DecisionResult が得られ、
フィールドごとの信頼度、確率、生の答え、そしてその呼び出しの usage とルーティングが含まれます:
result = agent.decide(state, schema=Ticket, return_details=True)
result.values["department"] # "billing"
result.answer_confidence["department"] # 0.94 max(p): the quantity min_confidence gates on
result.confidence["department"] # 0.71 normalized entropy, which depends on label count
result.probabilities["department"] # {"billing": 0.94, "support": 0.06, "sales": 0.0}
result.usage # {"input_tokens": 42, "output_tokens": 0}
result.routing # the Router decision, when a Router answered
confidence と answer_confidence は別の量であり、名前はそれぞれの定義に従っています。
answer_confidence は max(p)、つまり報告されている答えに乗っている確率の質量です。これこそが
温度スケーリングがフィットする対象であり、このリポジトリのあらゆるキャリブレーションの数値が
その上で計算される対象であり、min_confidence が比較される対象です —— これが confidence では
なくこちらでゲートすべき理由です。confidence は正規化エントロピーで、質問がいくつの選択肢を
持っていたかに依存します。tests/test_confidence.py は、2 選択肢の分布が noul では 0.90、
等価な choice では 0.53 で返ることを固定しているので、これをしきい値と比較することは
できません。使える answer_confidence を報告しなかったフィールドは None に対応し、これは
0.0 が報告された場合と同じではありません。
ゲートには、ゲート自身が使うのと同じ量を使ってください:
if result.answer_confidence["department"] < 0.6:
result.values["department"] = "human-review"
answer_confidence がフィルタに使うのに適した数値であることは、それが信頼できる確率で
あることとは違います。「c で返された答えのうち約 c の割合が正しい」と読めるのは、その
チェックポイントと質問形について、温度がフィットされ、ホールドアウトデータで検証された後に
だけです。同梱のチェックポイントは、同梱状態のままでは過信傾向があり、laya-multilingual には
フィット済みの温度がまったく同梱されていません —— README の
Calibration と
Honest limits の各節、そしてフィットの
ループについては
ファインチューニング notebook
を参照してください。このレベルに依拠する前にフィットしてください。これを報告するのは、
ゲートと評価 harness の両方が使う量だからです。
内部でどう対応するか
- Enum と
Literalは、値を文字列ラベルとするchoiceの質問になります。ラベルは返すときに 元の値へ写し戻されるので、整数は整数のままです。 - 上限付き整数は、値ごとに 1 レベルの
scoreの質問になります。返される値はminimum + argmaxです。 - 真偽値は
noulの質問になります。値はnoul >= 0.5です。 descriptionは質問の指示になります。したがって、よい記述こそが意思決定を正確にします。 これはフックガイドと同じ規則に従います。各選択肢が何を意味するかを明示して ください。nullの枝はフィールドが計画される前に落とされるので、Optional[X]はXが尋ねる質問を ちょうど尋ねます。そのフィールドに答えがないときは、キーが値から単に欠けます。これが、 モデルが見るものを変えずにフィールドを任意と宣言しても安全な理由です。
参照
- 予測フック:これが生み出す意思決定を観察、整形、キャッシュ、あるいはゲートします。
- 意思決定プリミティブ:
choice、score、noulを掘り下げます。 - LangChain と LangGraph:
LayaDecisionはこの呼び出しをチェーン内の runnable に したものです。