応答ストリーミングやプロンプト キャッシュなど、AI アプリケーションのパフォーマンスとコストを最適化するための一般的な手法については、これまで何度も説明してきました。今日は、実際の AI アプリを構築する上で、これとは異なるものの同様に重要なことについて話したいと思います。彼は、 構造化された機械可読出力。
これまで共有した例のほとんどでは、AI モデルからの自由テキストの応答を扱っています。ユーザーが質問すると、モデルは自然言語で応答し、その応答を何らかの方法でユーザーに表示します。まったくシンプルで簡単です。しかし、後でプログラムで処理できるように、モデルが特定の形式 (JSON オブジェクトなど) でデータを返す必要がある場合はどうなるでしょうか?テキストや画像から特定のフィールドを抽出したり、データベース エントリにデータを入力したり、その応答に基づいて後続のアクションをトリガーしたりするためのモデルが必要な場合はどうすればよいでしょうか?そのような場合、壁のようなテキストを取り戻すのはあまり便利ではありません。 🤔
幸いなことに、この問題には多くの解決策があります。 LLM から構造化された機械可読出力を取得するには、主に 2 つのアプローチがあります。 JSONモード そして 関数呼び出し (設備利用とも言います)。これら 2 つはよく混同されますが (どちらも構造化された出力を扱うため、これは当然のことです)、目的はまったく異なります。これに加えて、OpenAI は、と呼ばれる関数呼び出しの厳密なバージョンを導入しました。 構造化された出力これにより、スキーマの適用がさらに一歩進んだことがわかります。この投稿では、3 つすべてを詳しく見て、それぞれが内部でどのように機能するかを理解し、それぞれをいつ使用するかを理解します。
それでは、見てみましょう!
1. JSON モードとは何ですか?
JSON モードは、LLM から機械可読な出力を取得する簡単な方法です。これは基本的に、API リクエストで設定してモデルに指示できるパラメーターです。 いつも 有効な JSON オブジェクトを返します。必要なのはこれだけです。ただし、JSON の構造やスキーマ (スキーマ、フィールド名、型などを定義していないことを思い出してください) が有効で解析可能な JSON であるという保証はなく、この単純さには代償が伴います。
たとえば、Python で OpenAI の API を使用すると、パラメータを追加することで JSON モードを有効にできます。 response_format={"type": "json_object"} モデル募集のため。より具体的には、次のようになります。
from openai import OpenAI
client = OpenAI(api_key="your_api_key")
response = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "You are a helpful assistant. Always respond in JSON format."
},
{
"role": "user",
"content": "Extract the name, age, and city from this text: 'Maria is 32 years old and lives in Athens.'"
}
]
)
print(response.choices[0].message.content)
そして、応答は次のようになります。
{
"name": "Maria",
"age": 32,
"city": "Athens"
}
そして出来上がり!単純なパラメーターを変更するだけで、毎回有効な JSON が返されます。文字列の解析や奇妙な正規表現のハックは必要ありません。
ただし、落とし穴があります。 JSON モードでは、出力が次のとおりであることが保証されます。 有効なJSONしかしそれは起こります いいえ 特定の構造を保証します。同じ例を複数回実行すると、毎回わずかに異なるフィールド名またはわずかに異なる構造が得られる可能性があります。たとえば、ランバックは次のような場合があります。 "name" そしてもう一つ "full_name"。これは、特定のフィールドをプログラムで確実に抽出しようとする場合に問題になります。
2つ目は、設定を超えているということです response_format={"type": "json_object"}システム プロンプトで JSON で応答するようにモデルに常に明示的に指示することをお勧めします。上の例では、どのように追加したかに注目してください。 「常にJSON形式で応答する」 システムプロンプト内。これがないと、モデルは有効な JSON を返すことがありますが、動作が予測できない可能性があるため、常にではありません。
2. 関数呼び出しとは何ですか?
より高度なアプローチは、関数を呼び出して (またはツールを使用して)、LLM から構造化された機械可読な出力を取得することです。応答を JSON としてフォーマットするようにモデルに指示する代わりに、特定の プラン。つまり、出力に従う構造の正式な記述を明示的に定義するため、モデルはそのスキーマに正確に一致するデータを返すようになります。言い換えれば、関数呼び出しでは、どのようなフィールドが期待されるのか、それらのフィールドはどのような型であるべきか、どれが必須でどれがそうでないかなどを事前に定義します。
関数呼び出しを使用した同じ抽出例は次のようになります。
from openai import OpenAI
import json
client = OpenAI(api_key="your_api_key")
# define the schema of the output we expect
tools = [
{
"type": "function",
"function": {
"name": "extract_person_info",
"description": "Extract personal information from a text",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The full name of the person"
},
"age": {
"type": "integer",
"description": "The age of the person"
},
"city": {
"type": "string",
"description": "The city the person lives in"
}
},
"required": ["name", "age", "city"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o-mini",
tools=tools,
tool_choice={"type": "function", "function": {"name": "extract_person_info"}},
messages=[
{
"role": "user",
"content": "Extract the name, age, and city from this text: 'Maria is 32 years old and lives in Athens.'"
}
]
)
# parse the structured output
tool_call = response.choices[0].message.tool_calls[0]
result = json.loads(tool_call.function.arguments)
print(result)
そして出力は次のようになります。
{
"name": "Maria",
"age": 32,
"city": "Athens"
}
関数呼び出しを使用したこの例の出力は、JSON モードを使用して取得された出力と似ています。それでも、主な違いは、JSON モードとは異なり、関数呼び出しでは出力の一貫性が保たれることです。それは起こるだろう いつも 一貫したフィールド名、タイプ、および定義したその他の属性を使用して、正確に定義されたスキーマに従います。
🍨 データクリーム は、AI、データ、テクノロジーに関するストーリーやチュートリアルを特集したニュースレターです。これらのトピックに興味がある場合は、 ここから購読してください!
ボーナス: 関数呼び出しについてもう少し詳しく
構造化された出力に進む前に、単に構造化された出力を取得することをはるかに超えた、関数呼び出しの背後にある基本的な動機と使用方法について立ち止まってさらに拡張する価値があります。基本的に、関数呼び出しの概念は、エージェント AI ワークフローの基礎です。より具体的には、エージェント設定では、LLM は次のようになります。 ただ反応しないだけ ユーザーの質問に対して、むしろそれは 意思決定から ユーザー入力に基づいて次に実行するアクション。
たとえば、ユーザーの質問に応じて、注文を確認したり、返金を行ったり、人間のエージェントを派遣したりできるカスタマー サポート アシスタントを想像してみましょう。関数呼び出しを使用すると、これら 3 つの候補アクションを「ツール」(関数) として定義でき、モデルの出力により、入力に基づいてどのアクションを呼び出すか、どの引数を使用するかが定義されます。
tools = [
{
"type": "function",
"function": {
"name": "lookup_order",
"description": "Look up the status of a customer order",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order ID"}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "issue_refund",
"description": "Issue a refund for a customer order",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"reason": {"type": "string"}
},
"required": ["order_id", "reason"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o-mini",
tools=tools,
messages=[
{"role": "user", "content": "I want a refund for order #12345, it arrived broken."}
]
)
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name) # "issue_refund"
print(tool_call.function.arguments) # '{"order_id": "12345", "reason": "arrived broken"}'
したがって、API 応答オブジェクトは次のようになります。
ChatCompletionMessage(
content=None,
role='assistant',
tool_calls=[
ChatCompletionMessageToolCall(
id='call_abc123',
type='function',
function=Function(
name='issue_refund',
arguments='{"order_id": "12345", "reason": "arrived broken"}'
)
)
]
)
そして、print ステートメントは、仮に次のように出力します。
issue_refund
{"order_id": "12345", "reason": "arrived broken"}
それで、ここで何が起こっているのでしょうか?モデルは 1 を返します tool_calls 通常のテキスト応答ではなくオブジェクト (確認方法)content は None)。の中で tool_calls オブジェクトの場合、モデルが呼び出しを決定したことがわかります。 issue_refund (いいえ lookup_order)、ユーザーの発言に基づいて引数が自動的に入力されます。次に、それらの引数を解析し、システムで実際の返金ロジックを実行します。
モデルは要求されたデータを返すだけでなく、 決めた 候補者が行うのに最も適した仕事を選択し、その回答に対する適切な理由を記入してください。このようにして、これらの引数を取得し、システム内で対応するアクションを実際に実行できます。これが関数呼び出しの真の力であり、関数呼び出しがエージェント型 AI アプリケーションの基本的なコンポーネントである理由です。
ただし、ここでは機械可読出力に戻りましょう。エージェントの AI ワークフローと関数呼び出しについては、別の投稿で詳しく説明します。
3. 構造化された出力についてはどうですか?
関数呼び出しの厳密なバリエーションは、構造化出力です。関数呼び出しは、定義されたスキーマに従って出力を提供するようにモデルをガイドしますが、そうではありません。 実際には ハードバウンド。実際には、これは、この定義されたスキーマからの逸脱が依然として発生する可能性があることを意味します。このような逸脱が発生する可能性があります。
- 実際、必須としてマークされたフィールドは、モデルがその値を見つけるのに苦労した場合にはスキップされます。
- スキーマで定義されていないフィールドが追加されました
- フィールドは次のように定義されます
integer文字列として返されます"32"の代わりに32
…等々。
これは、関数呼び出しではモデルが 私は努力しています スキームに従いますが、これは依然としてベストエフォート世代です。他の LLM 出力と同様に、ここでの出力も基本的にはトークンを 1 つずつ予測しており、スキーマは単なるより強力な信号です。途中のどこかでトークンごとの生成が軌道から外れ、定義されたスキーマから逸脱した出力が生成される可能性がまだ十分にあります。
一方、構造化出力では、定義されたスキーマ内の各フィールドが常に定義どおりに出力に表示され、予期せぬフィールドの欠落や余分なフィールドがないことが保証されるため、関数呼び出しがさらに一歩進みます。主な差別化要因は、OpenAI が 制約付きデコード 舞台裏。これは、各トークン ステップで、モデルはスキーマに従って出力を有効に保つトークンの生成のみを許可されることを意味します。つまり、スキーマはシステム プロンプトを通じて単に要求されるのではなく、世代レベルで適用されます。
OpenAIの構造化出力は設定するだけで有効化できます strict: true 関数定義では次のようになります。
tools = [
{
"type": "function",
"function": {
"name": "extract_person_info",
"strict": True, # enables Structured Outputs
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"city": {"type": "string"}
},
"required": ["name", "age", "city"],
"additionalProperties": False
}
}
}
]
しかし、やはりコストがかかります。構造化出力は GPT-4o 以降のモデルで利用できますが、古いモデルは JSON モードに戻ります。すべての JSON 構造がサポートされているわけではなく、OpenAI が結果を前処理するため、処理が少し遅くなる可能性があります。
それにもかかわらず、これはモデルの出力に特定のスキーマを適用する最も厳密で安全な方法であり、逸脱の余地はありません。信頼性と安定性が本当に重要な運用システムの場合、これが一般に最も安全なオプションです。
しかし、これらはすべて同じことではありませんか?
JSON モード、関数呼び出し、構造化出力は、基本的にすべてモデルから JSON を取得するため、同じことをしているように見えるかもしれません。しかし、すでに見たように、それらは何を保証するのか、何のために設計されているのかという点で大きく異なります。特に:
- スキーマの強制:JSON モードは有効な JSON を返しますが、構造的な保証はありません。関数呼び出しは、特定のフィールド名、型、必須フィールドに準拠して、定義されたスキーマに一致する有効な JSON を返しますが、逸脱する可能性は依然としてあります。構造化出力はさらに一歩進んで、そのスキーマを生成レベルで強制し、逸脱を不可能にします。
- 例: JSON モードは、機械可読な応答が必要だが、不変形式でも使用できる場合に使用します。関数呼び出しは主に、モデルがアクションをトリガーするか外部ツールに引数を渡す必要がある場合のために設計されており、本質的には機械可読出力の一般的なケースです。構造化出力は信頼性が保証された関数呼び出しであるため、出力の一貫性が必要な運用パイプラインに最適です。
- セットアップの簡単さ: JSON モードはセットアップが最も軽いオプションです。スキーマ定義を行わずにパラメーターを変更するだけです。一方、関数呼び出しと構造化出力の場合は、JSON スキーマについて考えて設定する必要もあります。
そうは言っても、OpenAI 自体は、原則として、可能な限り JSON モードではなく常に構造化出力を使用することを推奨しています。

私の心の中で
LLM から機械可読な出力を取得し、そのための適切なアプローチを選択すると、AI アプリケーションの信頼性と保守性に大きな違いが生じます。フリーテキストの応答は会話型インターフェイスには最適ですが、LLM がより大規模なシステムのコンポーネントになった瞬間 (例: データを下流に供給する、アクションをトリガーする、データベースにデータを入力するなど)、構造化された応答が不可欠です。 JSON モード、関数呼び出し、構造化出力では、それぞれ異なる厳密性レベルでこのような出力を提供できます。 AI エンジニアリングにおける多くの意思決定と同様、正しい選択は、何を構築するか、およびどの程度の変動を許容できるかによって決まります。
ここまで進んだなら、 ピアゴリズムが役立つかもしれません – 私たちは、チームが組織の知識を 1 か所で安全に管理できるようにするプラットフォームを構築しています。
この投稿は気に入りましたか?参加してください 💌サブスタック そして💼リンクトイン
特に明記されていない限り、すべての画像は作者によるものです。









Leave a Reply