Skip to content
This page has been auto-translated and may contain errors.View in English

構造化された出力

docs.scrimba.com

まず、問題の形を理解しましょう。構造化された出力とは、コードが直接読み取ることができるデータをモデルに返させる実践のことです。手でパースしなければならない段落ではなく、分岐できるフィールドです。この章は、これを信頼できるようにする方法についてです。

これまで、モデルはテキストを返してくれていました。人間が読むときはいいのですが、ソフトウェアは段落ではあまり何もできません。コードでモデルの答えに対応するには、データとして必要です。読み取れるフィールド、分岐できる値、保存できるレコード。

この転換は小さいですが、下流のすべてを変えます。モデルが「顧客は不満に見える」という文の代わりに { "sentiment": "negative" } を返すようになると、コードはチケットをルーティング、ダッシュボードを更新、またはアラートを送信できます。興味深い部分は、テキストを予測するだけのモデルにきれいなデータを作らせる方法で、その答えはあなたが知らなかったレバーを明かします。

モデルはテキストを返します。コードはデータが必要です。読み取るフィールド、分岐する値、保存するレコード。この章はその二つの間の橋で、モデルがいい形式にしてくれるのを望むのをやめて、出力できることを制限し始めます。

ここに本当のレバーがあります。プロンプティングのトリックではありません。LLMの仕組みのサンプリングループは、出力が必要な形にされるように操舵できます。これにより、ぼやけたテキストジェネレーターがシステムの残りの部分が頼ることができるコンポーネントになります。

散文を返すモデルは組み立てられないコンポーネントです。その出力が別のシステムに供給される瞬間、既知の形状のデータが必要であり、そのデータを取得する失敗モードは見て処理できるものである必要があります。

この章は生成をスキーマに制限することについてです。レイテンシでそれが何を引き起こすか、それが何を買わないか(正しい値)、そしてそれが端で失敗する方法です。下の機構はツール使用を駆動するものと同じです。ツール呼び出しはスキーマの代わりに関数シグネチャを持つ構造化された出力です。

JSONをリクエストする

最初の本能はプロンプトで「**JSON**で返してください」とお願いすることです。JSON は、ソフトウェアがデータを渡すのに使う平文形式で、中括弧の中のフィールドと値として書きます。試してみるとほとんどの場合うまくいきます。これが罠です。モデルは相変わらずテキストを予測しているだけで、時々その予測されたテキストはコードフェンス、またはデータの前の親切な「もちろん、どうぞ!」、またはそれの後ろのトレーリングコメントになります。これらのいずれもがJSONを読むコードを壊します。丁寧に頼むのはJSONへの予測をバイアスさせます。それを強制しません。

だからプロバイダーは本当のレバーをあなたに与えます。response_formatでJSONモードをオンにするのは、尋ねること以上のことをします。それはモデルを制限して、構文が有効なJSONとして返ってくるようにします。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": '都市と気温を抽出してください。JSON で返してください: { "city": string, "tempC": number }。'},
        {"role": "user", "content": "東京は今約18度です。"},
    ],
    response_format={"type": "json_object"},  # 出力を有効なJSON構文に制限
)

data = json.loads(response.choices[0].message.content)
print(data["city"], data["tempC"])  # "東京" 18

response_format={"type": "json_object"} は出力を有効なJSON構文にするので、json.loads は迷ったテキストで窒息しません。あなたはまだプロンプトで必要なフィールドを記述する必要があります。JSONモードは有効なJSONを約束しますが、特定のフィールドを約束しません。「JSON ではない」を排除します。「間違った形のJSON」ではなく。それでも約束できないこと:返信が途中で切れた場合、半分のJSONオブジェクトを受け取ることができるので、信頼できる構文ではなく、すべてのケースでの強い保証ではありません。

JunoJSON をリクエストする プロンプトだけでJSONをリクエストするのは不安定です。モデルはただ確率の高いテキストを予測するだけで、フェンスやチャットで囲んでパースを破るかもしれません。`response_format` をJSONモードに設定することは、構文を制限するので、それは有効なJSONとして返ってきます。フィールドがあなたが頼んだものと一致することを約束しないので、プロンプトで形を記述して、何が到着したかをチェックしてください。

反射的に「JSON で返してください」とプロンプトに書きます。JSON はソフトウェアがデータを渡すのに使うデータ形式です。ほとんどの場合うまくいきます。これは正確に問題です。ほとんどは契約ではありません。モデルはテキストを予測し、テキストには時々コードフェンス、「わかりました!」の前置き、またはトレーリングノートが含まれます。これらはすべて json.loads を壊します。

JSONモードは最初の本当のレバーです。response_format をJSONオブジェクトタイプに設定すると、生成を制限するので、構文は有効なJSONになります。それのように見えるテキストではなく。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": '都市と気温を抽出してください。JSON で返してください: { "city": string, "tempC": number }。'},
        {"role": "user", "content": "東京は今約18度です。"},
    ],
    response_format={"type": "json_object"},  # 出力を有効なJSON構文に制限
)

data = json.loads(response.choices[0].message.content)
print(data["city"], data["tempC"])  # "東京" 18

ここの response_format の形はOpenAIのもの。他のプロバイダーは同じ考えを独自のフィールドの下で公開しているので、リテラルキーではなくコンセプトをポートしてください。JSONモードは構文を制限します。フィールドの形は決してありません。プロンプトではフィールドを説明し、モデルは有効なJSONを発行しながら、それらの名前を変更、削除、または異なり方で入れ子にできます。

さらに一つの隙間があります。JSONモードは、応答が完了した場合にのみ、形成されたJSONを約束します。max_tokens 上限に達するとオブジェクトの途中で、切り詰められた解析不可能な出力が得られます。だからパースガードが必要です。

JunoJSON をリクエストする JSONモードは構文を制限して、モデルはデータをフェンスとチャットで囲むのをやめます。でも決してフィールドをピンで留めません。モデルはまだ名前を変更、削除、または異なり方で入れ子にできます。`response_format` キーはOpenAIのもの。他のプロバイダーは異なる綴りです。そして約束は完了した応答にのみ当てはまります。`max_tokens` で切り詰められた返信は解析不可能です。とにかくパースを保護してください。

プロンプトの「JSON で返してください」は提案であり、提案は負荷の下で失敗します。ここにコードフェンス、そこに前置き、モデルが会話的な気分のときはトレーリングコメント。それぞれが本番環境で json.loads 例外です。JSONモード(ここで response_format={"type": "json_object"})は提案を生成された構文への制約に置き換えるので、戻ってくるバイトはJSONとしてパースします。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": '都市と気温を抽出してください。JSON で返してください: { "city": string, "tempC": number }。'},
        {"role": "user", "content": "東京は今約18度です。"},
    ],
    response_format={"type": "json_object"},  # 出力を有効なJSON構文に制限
)

data = json.loads(response.choices[0].message.content)
print(data["city"], data["tempC"])  # "東京" 18

これが何を制限するかについて正確に。JSONモードは構文をピンで留めます。スキーマではなく、完了した応答だけです。 モデルはまだフィールド名を選択でき、必要なフィールドを省略でき、どのように入れ子にすることもできます。

保証は生成の終了に条件付きです。モデルがオブジェクトの途中で max_tokens に達する場合、切り詰められた文字列を取得し、それは有効なJSONではありません。つまり、JSONモードが削除しない失敗モードは、リトライパスが最も処理する必要があるものです。示されている response_format キーはOpenAIのもの。他のプロバイダーの同等物は別の名前の下にあるので、コンセプトを移植可能と扱い、リテラルフィールドは扱いません。形の保証にはスキーマが必要で、それは次のセクションです。

JunoJSON をリクエストする JSONモードは完了した応答の構文を制限します。それ以上ではありません。モデルはまだフィールド名を付け、省略し、入れ子にします。`max_tokens` で殺された生成は切り詰められた解析不可能な出力をあなたに渡します。その切り詰めケースはあなたのエラーパスが所有する必要があります。モードは削除しません。`response_format` の形はOpenAIのもの。だからアイデアをポートしてください。キーではなく。形のためにはスキーマが必要です。

スキーマで形を固定する

JSONモードは構文を有効にしますが、モデルはまだフィールドの名前を変更、削除、または異なり方で入れ子にする可能性があります。その余裕を削除するために、**スキーマ**をそれに与えます。出力が持つ必要があるフィールドと型の正確な説明。モデルが埋める必要がある形。strict: true でその形に一致することが保証されます。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "メッセージから連絡先の詳細を抽出してください。"},
        {"role": "user", "content": "こんにちは。田中太郎です。[email protected] で連絡してください。"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "contact",
            "strict": True,  # スキーマを正確に強制
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                },
                "required": ["name", "email"],
                "additionalProperties": False,
            },
        },
    },
)

contact = json.loads(response.choices[0].message.content)
# {"name": "田中太郎", "email": "[email protected]"}

モデルがトークンを予測するだけのときに、スキーマがどのように強い保証になるのでしょうか?プロバイダーが予測することを許可されているトークンを制限するからです。各ステップでモデルはまだすべての可能な次のトークンのランクを付けますが、システムはスキーマを破るものを削除し、モデルは残ったものから選択します。

スキーマが次のフィールドが email である必要があると言う場合、他のフィールドを開始するトークンはモデルが選択する前にテーブルから削除されます。形の外側のトークンが削除されるので、モデルは形の外側に漂流できません。

知る価値のあるより柔らかい効果があります。選択したフィールド名は、値を予測するとき、モデルが読む一部になるので、明確な名前はより良い答えをガイドします。tempC を埋めるように求められると、モデルはセルシウス数を支持しがち。value を埋めるように求められると、それは進むのが遠いです。フィールドを明確に名前付けすることは、部分的には命令、部分的にはスキーマです。

Junoスキーマで形を固定する スキーマはモデルが埋める必要がある形で、`strict: true` でそれは正確なフィールドと型を取得します。システムが形を破るトークンを削除してからモデルが選択するため、実行不可能ではなく、強制です。フィールドを明確に名前付けしてください。`tempC` は `value` が遠くでガイドする場所、セルシウス数に向かってモデルを傾けるため。`tempC` がそれが本当に働いたことを信じるまでには時間がかかりました。

JSONモードはフィールドを解放したままパスします。名前を変更、削除、または再入れ子にすることができます。スキーマはそのギャップを閉じます。フィールドと型の正確な説明で、strict: true で出力はそれに一致することが保証されます。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "メッセージから連絡先の詳細を抽出してください。"},
        {"role": "user", "content": "こんにちは。田中太郎です。[email protected] で連絡してください。"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "contact",
            "strict": True,  # スキーマを正確に強制
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                },
                "required": ["name", "email"],
                "additionalProperties": False,
            },
        },
    },
)

contact = json.loads(response.choices[0].message.content)
# {"name": "田中太郎", "email": "[email protected]"}

メカニズムは**制約付きデコーディング**:サンプリングステップに適用されたトークンプルーニング。すべてのポジションでモデルは通常のランキングをすべてのトークンに対して生成します。システムは、スキーマに一致し続けるために出力を作るのを止めるトークンをマスクアウトして、モデルは生き残ったものからサンプリングします。文法が次のトークンが email フィールドを開く必要があると言う場合、他のものを開始するトークンはドローの前にゼロ化されます。モデルはその形の外に動くことができません。その形の外への動きがボードから削除されるためです。要求で落胆しているのではなく。

実践的な2つのハンドル。フィールド名は命令なので、必要な値のためにそれらに名前を付けてください。tempCvalue が推測を残すセルシウスに向かってモデルをステアします。フィールドの説明がより強くステアします。そしてその同じ制約は enum を強制します(許可された値の固定リスト)またはその数範囲。これが分類を信頼できるようにします。ラベルポジションでのみ、リストされたラベルはマスクをサバイブします。モデルはリストにない型を製造することはできません。

Junoスキーマで形を固定する `strict: true` でモデルが形を壊すすべてのトークンをマスクアウトしてからサンプリング。形は要求されていなく、強制されます。それに頼ってください。フィールド名と説明は命令です。`tempC` を`value` ではなく名前付けて、モデルはそれをよりよく埋めます。同じマスキングが `enum` を強制します。これが分類を保持する理由。モデルはリストにないラベルを作成することはできません。

JSONモードは解析可能なバイトを取得します。スキーマは既知の形を取得します。strict: true で出力は宣言するフィールドセットと型に一致することが保証されます。これは盲目的にインデックスできるデータと防御的にプローブする必要があるデータの違いです。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "メッセージから連絡先の詳細を抽出してください。"},
        {"role": "user", "content": "こんにちは。田中太郎です。[email protected] で連絡してください。"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "contact",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                },
                "required": ["name", "email"],
                "additionalProperties": False,
            },
        },
    },
)

contact = json.loads(response.choices[0].message.content)

メカニズムは**制約付きデコーディング**で、それを一から理解したいのは、そのコストがタダではないからです。モデルはステップの各ステップで語彙内のすべてのトークンに確率を発行します。制約付きデコーディングはスキーマを文法(どのトークン列が法的であるかのルールセット)にコンパイルしてから、各ステップで、すべての違法なトークンの確率をゼロに設定するマスクを構築するので、サンプリングは出力が有効であり続けるトークンにのみ着地できます。形は構築によって実行されます。問い合わせによってではなく。

そのコンパイルは無料ではなく、レイテンシとして表示されます。新しいスキーマに対する最初の呼び出しは、文法を構築するのに一度のコストを支払います。ので、コールドスタートレイテンシは新しいスキーマで定常状態より高いです。スキーマを安定させて、リクエストごとに生成されるのではなく、再利用したほうが、そのコンパイルを何度も支払いません。

additionalProperties: false と完全な required リストは装飾ではありません。それらは、システムが追加または欠落フィールドを完全に拒否でき、スキーマ駆動拒否をサポートするプロバイダーで、モデルは不正形成されたオブジェクトの代わりに構造化された拒否を返すきれいな方法でもあります。それらを削除し、有効としてカウントされるものを広げます。これが厳密モードに達した理由の逆です。

いくつかの本番環境現実。厳密なサポートは不均一です。一部のプロバイダーは本当の文法を実行するか、他のプロバイダーはそれを近似するか、JSONスキーマのサブセットのみをサポート(pattern なし、制限されたネスト)。だからプロバイダーがスペックではなく実際に栄誉を与えるものをテストしてください。部分的およびストリーミング出力は厄介な事例です。トークンがストリーミングされる中、構文的に不完全なオブジェクトを保ったままです。だから、パースする前に完了にバッファするか、半構築構造を許容する増分パーサーを使用してください。そしてこれはツール使用と同じ機械です。関数の引数スキーマは同じ方法で制約されるので、ここで学ぶすべてはツール呼び出しを信頼できるようにするために直接転送されます。

Junoスキーマで形を固定する 制約付きデコーディングはスキーマを文法にコンパイルして、サンプリング前のすべての違法なトークンをゼロにします。形は構築によって実行されます。そのコンパイルは新しいスキーマに対する最初の呼び出しでレイテンシを支払うので、リクエストごとに1つをミントの代わりにスキーマを再利用します。`additionalProperties: false` と完全な `required` リストを保ち続けてください。これが拒否実行、きれいな構造化拒否を返す、いくつかのプロバイダーを強制するものです。厳密なサポートは異なります。ストリーミングは半オブジェクトをあなたに渡します。同じマスキングがツール呼び出し引数を駆動します。だからここで学ぶ何かがツール呼び出しの信頼性に二倍でお金を払います。

とにかく検証する

スキーマは形を制限しますが、とにかく外の世界からのデータとして出力を扱ってください。形は有効で、内容は間違っています。実は誤字の抽出されたメール。モデルが推測した数字。入力が含まれていないため、フィールドは空白のままです。そして呼び出しは max_tokens で切られて、切り詰められたJSONとして到着するなど、通常の方法で失敗する可能性があります。防御的にパースしてください。有効な形は正しい値ではありません。

python
import json

def parse_contact(raw):
    try:
        data = json.loads(raw)
        if not data.get("name") or not data.get("email"):
            return None  # 現在でも空
        return data
    except json.JSONDecodeError:
        return None  # 有効なJSONではない(例えば、切り詰められた返信)

contact = parse_contact(response.choices[0].message.content)
if not contact:
    pass  # 失敗を処理する:リトライ、再度尋ねる、または親切なエラーを表示

これはLLMの仕組みから**幻覚**の教訓は新しい衣装で。形が正しいことは値が正しいことを意味しません。スキーマは name フィールドを取得することを保証します。それは名前が正しいことを保証しません。try/except をパースの周りに加えて、信頼する前に値をチェックしてください。これは安い保険です。返信は形成されて、それでも間違っています。

Junoとにかく検証する スキーマは形を保証します。値の真実は決してありません。有効なレコードはまだ間違った値または空の値を保持でき、呼び出しは切り詰められて到着する可能性があります。パースを `try/except` でラップして、信頼する前に値をチェックしてください。検証が失敗するとき前もって何が起こるかを決定してください。後でスクランブルではなく、リトライまたは親切なエラー。

厳密なモードは形を保証します。内容ではありません。出力はスキーマに一致でき、それでも間違っています。誤字である確率の高いメール。必要なフィールドを埋めるために作成された数字。入力が何も保持していなかった空の文字列。そして、パースは max_tokens で切り詰めるかもしれません。だからあなたは検証します。毎回。

python
import json

def parse_contact(raw):
    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        return None, "unparseable"  # 切り詰められるか、形成されていない

    if not data.get("name") or not data.get("email"):
        return None, "empty_field"  # 現在でも空白
    if "@" not in data["email"]:
        return None, "bad_email"  # 形は良好で、値ではない
    return data, None

contact, error = parse_contact(response.choices[0].message.content)
if error == "unparseable":
    pass  # 1回をリトライします。おそらく切り詰め:`max_tokens` を上げるか入力を短くする
elif error:
    pass  # 値レベルの失敗をログして、フォールバック

パターンは、**防御的パース**で失敗モードを区別します。異なる処理が必要です。JSONDecodeError は通常切り詰めを意味するため、修正は max_tokens を上げるか入力を縮小してリトライすることです。空またはアウトオブレンジ値はコンテンツ失敗で、形式ではなく、だからその同じ呼び出しをリトライするのはほとんど役に立ちません。ログしてフォールバック。

「パースできない」を「パースしたが間違い」から分離してください。修正は異なります。これは幻覚に戻ります。制約付きデコーディングは、形成されていない出力失敗モードを削除し、その間違った値を完全に残しました。

Junoとにかく検証する 厳密なモードは形をピンで留めます。内容ではなく、毎回検証します。レコードは形成され、誤字、推測、または空白を保持できます。失敗を分割します。`JSONDecodeError` は通常切り詰め(`max_tokens` を上げるか入力を縮小してリトライ)を意味するが、不良値はコンテンツミスで、同じ呼び出しをリトライすることはそれを修正しません。制約付きデコーディングは、形成されていない出力問題を削除して、間違った値を無傷に残しました。

制約付きデコーディングは、形成されていない出力失敗モードを閉じ、他には何も閉じません。形は保証されます。内容はそうではありません。入力が決してサポートしなかった必要なフィールドは自信を持った推測で埋められます。数字はいかなる明智な範囲の外に着地します。メールはstrで解析されますが、誤字です。

形式保証自体は切り詰めで失効します。だから検証は任意の衛生ではなく、スキーマが構造的に取ることができないものを取る層です。

python
import json

def parse_contact(raw, retry):
    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        return None, "truncated"  # 不完全な生成。内容問題ではない

    if not data.get("name") or "@" not in data.get("email", ""):
        return None, "invalid_value"  # 形は保持しました。値はしなかった
    return data, None

contact, failure = parse_contact(raw, retry=False)
if failure == "truncated":
    pass  # `max_tokens` を上げるか入力をトリムしてから、呼び出しをリトライ
elif failure == "invalid_value":
    pass  # 修復パス:悪い出力を供給して修正するように再度尋ねるか、フォールバックにルート

盾を持ち上げるバリデータとあらゆるものを飲み込む try/except を分ける3つのこと:

  • 失敗を分類します。切り詰めは、より多くの max_tokens でリトライで修正される予算問題です。ブラインドリトライが再現する不良値はコンテンツ問題です。
  • **修復パス**をビルド:値レベルのケースについてはフォールバックのみではなく。無効な出力を指示で供給して修正する場合、多くの場合、それは1つの追加呼び出しで回復し、これはリクエストを失敗より安いですが、リトライキャップします。そして無限ループできるように間違った入力への持続を止めます。
  • コストをバウンド:すべてのリトライは別の請求された呼び出しと別のレイテンシラウンドトリップです。だからリトライ予算は、後付けではなく設計の一部です。

パース単独ではなく、値を検証します。 これは幻覚の抑制ストーリーが構造化データに適用されました。制約付きデコーディングはきれいな封筒をあなたに与えて、内部にあるものについて何も言いません。そして、封筒はチームが検証を停止し、焦げるところです。スキーマ検証、範囲とセマンティックチェック、そして境界リペアループは3層で、異なるものを取ります。スキーマは形をキャッチし、チェックは無意味な値をキャッチし、修復ループは救助可能なものを回復させます。

Junoとにかく検証する 厳密なモードはパース単独ではなく、コンテンツを削除して検証します。失敗を分類します。切り詰めは予算修正(より多くの `max_tokens`、リトライ)です。無効な値はコンテンツミスなので、盲目のリトライのみで再現し、悪い出力を供給して修復パスをビルドします。リトライをキャップして、悪い入力があなたの請求書をループできないようにします。きれいな封筒はチェックを停止し、焦げるところです。

2つのパターン:分類と抽出

ほとんどの構造化出力の作業は、2つの形のいずれかです。

**分類**は入力を固定されたラベルのセットの1つにソートします。スキーマの enum は、出力を正確にそれらのラベルに制限します。同じトークンプルーニングのトリックを使用します。ラベルポジションで、許可された値のみが予測される可能性があります。だから、モデルはリストにない型を作成することはできません。

python
# 分類のためのスキーマフラグメント
{"type": "string", "enum": ["billing", "technical", "general"]}

抽出は自由なテキストから特定のフィールドを引き出します。名前、日付、金額、商品名のリスト。一緒に、分類と抽出は現実のAI機能の大きなシェアをカバーします。サポートチケットのルーティング、コンテンツタグ付け、メールをレコードに変える、領収書を読む。両方は、ぼやけたテキストジェネレーターを依頼できるコンポーネントに変わります。

Juno2つのパターン:分類と抽出 分類は入力を固定されたラベルセットの1つにソートし、`enum` でロックされるため、モデルはリストにない型を作成することはできません。抽出は自由なテキストから名前付きフィールドをレコードに引き出します。両方は、ぼやけたテキストジェネレーターを依頼できるコンポーネントに変わります。実践的なAI機能の大きなシェアをカバーします。

構造化出力で構築するほぼすべてのものは、2つの形のいずれかで、それらの名前を付けるのは正しいスキーマに達することができます。

**分類**は、固定セットから1つのラベルに入力をマップします。enum は全仕事を実行します。制約付きデコーディングは、ラベルポジションでリストされたラベル以外のすべてのトークンをマスク裏返します。そしてモデルはそれらのいずれかまたは他に何もありません。

python
# 分類:閉じたセットから1つのラベル
{"type": "string", "enum": ["billing", "technical", "general"]}

抽出は自由なテキストから名前付きフィールドをレコードに引き出します。連絡先とチケットの例。2つは構成します。単一のスキーマは一度に分類と抽出できます。これはほとんどの現実の機能です。閉じたセットは enum を取得します。開く値は型付きフィールドを取得します。

されることに意識的に決定します。値が本当に固定セット(ステータス、カテゴリー、優先度)のとき、enum を使用して、モデルが漂流できません。開く値(名前、要約)のとき、平文の型付きフィールドを使用して、検証するのに十分な値を信頼することを受け入れます。それらを混ぜること。実世界が多くのケースを持つ場所 enum、または閉じたセットが会ったエラーを持つ場所、自由フィールド。これは、これらの機能がしずかに不正行為をする場所です。

Juno2つのパターン:分類と抽出 2つの形はほとんどのこれをカバーします。分類は、閉じたセット(`enum` は実行を強制)から1つのラベルに入力をマップし、抽出は名前付きフィールドをレコードに引き出し、1つのスキーマが同時に両方をできます。意図的にピック:固定セットのための `enum` そのため、モデルは漂流できません。開く値のための平文型付きフィールド。実世界のケースが不足している `enum` はラベルの静かなソースです。

構造化出力は2つのパターンに崩れます。そして区別は語彙だけではなく、設計レバーです。分類は入力を、閉じたセットから1つのラベルにマップし、enum で、制約付きデコーディングは値のハード選択に削減します。抽出は自由なテキストから型付きフィールドを引き出します。ほとんどの本番環境スキーマがそれらを結合します。

python
# 分類:制約付きデコーディングは、ラベルポジションでこれらのトークンのみを許可
{"type": "string", "enum": ["billing", "technical", "general"]}

レバーは、enum が鮮いエッジを持つ正確性の保証だからです。閉じられたセットの値(ステータス、優先度)は、モデルが何かオフリストを出力できないため、enum に属しています。そして、これは開いた終わりの生成を、下流のコードが頼ることができるチェックに変わります。

でも enum は、何も合うとき、選択を強制します。実入力がラベルのいずれにも一致しない場合、モデルは、それがフィットしないのをあなたに言う代わりに、最も近い間違ったものを選択するように制約されます。脱出ハッチなしの enum は「フィット」しないことを自信を持った間違ったラベルに変わります。 だからメッシーな実入力に直面しているクラシファイアのすべてのために、明確な**脱出ハッチ**、other または unknown メンバーを追加して、別の場所にルートするシグナルとして読んでください。

同じ注意は自信に当てはまります。ラベルに決定が乗っている場合、モデルはまた自信またはショート説明フィールドを出力さらます。そのため、境界コールが目に見えるので、きれいに見える enum 値に洗ったの代わりに。これらはツール使用と同じ本能です。そこで、モデルは閉じたセットから1つのツールを選択し、同じ「何もフィットしないのなら」問題に直面します。

Juno2つのパターン:分類と抽出 分類は、閉じたセットが `enum` で強制したセットで、抽出は自由なテキストから型付きフィールド。そして `enum` は、何も合わないとき、選択を強制し、「マッチしない」を自信を持った間違ったラベルに洗う、鮮いエッジを持つ保証です。`other` または `unknown` メンバーを追加してルーティング。ラベルに決定が乗っているとき、自信フィールドを出力させます。ツール使用で1つのツールを、閉じたセットから選ぶのと同じ形。

実際の使用

メッシーなサポートメールを構造化チケットに変わる。分類と抽出を1つのスキーマに結合:

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "サポートメールをチケットに変わってください。"},
        {"role": "user", "content": email_text},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "ticket",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "category": {"type": "string", "enum": ["billing", "technical", "general"]},
                    "urgency": {"type": "string", "enum": ["low", "medium", "high"]},
                    "summary": {"type": "string"},
                },
                "required": ["category", "urgency", "summary"],
                "additionalProperties": False,
            },
        },
    },
)

ticket = json.loads(response.choices[0].message.content)
# {"category": "billing", "urgency": "high", "summary": "先月の購読で二重課金されました。"}

1つのコールは、メールを2つの方法で分類して、要約を抽出し、コードがルートして保存できるレコードを返します。形は保証されます。値はまだ確認する価値があります。これまで、すべてがテキストを流入して流出させてきました。次は、モデルに完全に異なる感覚を与えます。埋め込みでは、テキストはあなた自身のドキュメントと機能する検索と財団の意味で比較できる数字になります。

Juno実際の使用 厳密なスキーマを持つ1つのコールは、メールを2つの方法で分類して、1つの呼び出しで要約を抽出し、コードがルートして保存できるレコードを返すことができます。形は保証されます。値はまだ確認する価値があります。ツール使用は後で、同じトリックに頼ります。ツールに送信されるモデル引数も構造化された出力です。

実際の機能は通常、1つのスキーマでパターンを両方結合します。サポートメールがチケットになります。2つの分類と1つのスキーマに1つの抽出。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "サポートメールをチケットに変わってください。"},
        {"role": "user", "content": email_text},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "ticket",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "category": {"type": "string", "enum": ["billing", "technical", "general"]},
                    "urgency": {"type": "string", "enum": ["low", "medium", "high"]},
                    "summary": {"type": "string"},
                },
                "required": ["category", "urgency", "summary"],
                "additionalProperties": False,
            },
        },
    },
)

ticket = json.loads(response.choices[0].message.content)
# {"category": "billing", "urgency": "high", "summary": "先月の購読で二重課金されました。"}

2つの enum は厳しく制約されるので、categoryurgency は常にルーティング可能です。summary は開いたフィールドなので、それをルートする前に検証する1つです。その分割、強制されたラベルと確認された自由なテキストフィールドは、ほとんどの抽出機能の形です。次に、埋め込みはモデルに異なる感覚を与えます。テキストは意味で比較できる数字に変わります。検索の財団とあなた自身のドキュメントで機能します。

Juno実際の使用 厳密なスキーマ、2つの強制された `enum` と開いた `summary` フィールド。ラベルは安全にルートします。開く `summary` はハルシナが値を隠し、長い生成が `max_tokens` で切り詰めにトリップするフィールドです。だからそれに対して特に予算して検証してください。これはツール使用と同じ機械です。引数は関数スキーマに対して制約されます。ので、すべての習慣がここに、検証、修復、脱出ハッチ、エージェントを信頼できるようにするために直接運ばれます。

毎日の本番環境の形は複合体です。分類2つの軸上で、開く自由テキストフィールドを抽出。すべて1つのスキーマと1つの呼び出しで。

python
import json

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": "サポートメールをチケットに変わってください。"},
        {"role": "user", "content": email_text},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "ticket",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "category": {"type": "string", "enum": ["billing", "technical", "general"]},
                    "urgency": {"type": "string", "enum": ["low", "medium", "high"]},
                    "summary": {"type": "string"},
                },
                "required": ["category", "urgency", "summary"],
                "additionalProperties": False,
            },
        },
    },
)

ticket = json.loads(response.choices[0].message.content)
# {"category": "billing", "urgency": "high", "summary": "先月の購読で二重課金されました。"}

実行はフィールド全体で不均一で、そして、それは設計ポイントです。enum はハード保証で、categoryurgency はさらなるチェックなしでルートするのに安全です。summary は制約されていない文字列なので、きれいなスキーマの後ろに隠蔽された幻覚またはオフベースの値は正確にできます。そしてフィールドは検証パスを獲得します。2つの強制されたラベルは、開いた summary フィールドで、切り詰めで最も露出されるスキーマです。長い生成要約は max_tokens にトリップして、全オブジェクトを解析不可能にします。だからラベルではなく、自由テキストフィールドのための予算。

これはツール使用と同じ機械です。ツール呼び出しはモデルが関数スキーマに対する構造化された引数を出力します。同じ方法で制約されます。だから検証、修復、そして enum 脱出ハッチ直感があります。それはホールセールで、代理店を信頼できるようにするために転送されます。ここから、埋め込みは、モデルを異なるモードにシフトさせます。テキストはあなた自身のドキュメントに接地された回答と検索の基礎である意味で比較できるベクトルです。

Juno実際の使用 1つの厳密なスキーマ。2つの強制された `enum` と開いた `summary`:ラベルはルート安全です。自由なテキストフィールドはハルシナの値が隠れ、長い生成が `max_tokens` を切り詰めにトリップする場所です。だからそれに対して特に予算して検証してください。これはツール使用と同じ機械です。引数は関数スキーマに対して制約されます。だからここで習慣。検証、修復、脱出ハッチ。エージェントを信頼できるようにする。