संरचित आउटपुट

पहले, समस्या का आकार। संरचित आउटपुट मॉडल को ऐसा डेटा लौटाने की प्रथा है जिसे आपका कोड सीधे पढ़ सकता है, एक फ़ील्ड जिसे आप शाखा दे सकते हैं, न कि एक पैराग्राफ जिसे आपको हाथ से पार्स करना हो। यह अध्याय उसे विश्वसनीय बनाने के बारे में है।
अब तक मॉडल आपको टेक्स्ट देता है, जो अच्छा है जब कोई व्यक्ति इसे पढ़े। लेकिन सॉफ़्टवेयर पैराग्राफ से ज्यादा कुछ नहीं कर सकता। मॉडल के उत्तर पर कोड में कार्य करने के लिए, आपको इसकी जरूरत है डेटा के रूप में: एक फ़ील्ड जिसे आप पढ़ सकते हैं, एक मान जिस पर आप शाखा दे सकते हैं, एक रिकॉर्ड जिसे आप सहेज सकते हैं।
बदलाव छोटा है लेकिन यह डाउनस्ट्रीम में सबकुछ बदल देता है। एक बार जब मॉडल { "sentiment": "negative" } लौटाता है, तो एक वाक्य के बजाय इस बारे में कि ग्राहक कैसे असंतुष्ट लगता है, आपका कोड टिकट को रूट कर सकता है, एक डैशबोर्ड अपडेट कर सकता है, या एक सतर्कता भेज सकता है। दिलचस्प हिस्सा यह है कि जब मॉडल केवल टेक्स्ट की भविष्यवाणी करता है तो आप इसे स्वच्छ डेटा कैसे उत्पन्न करते हैं, और उत्तर एक लीवर को प्रकट करता है जिसे आप नहीं जानते थे।
JSON माँगें
पहली वृत्ति प्रॉम्प्ट में पूछना है: "JSON" के रूप में उत्तर दें", सादे-पाठ प्रारूप जो सॉफ़्टवेयर डेटा पास करने के लिए उपयोग करते हैं, घुंघराले ब्रेसिज़ के अंदर फ़ील्ड और मान के रूप में लिखे गए हैं। इसे आजमाएं और यह ज्यादातर काम करता है, जो जाल है। मॉडल अभी भी संभावित टेक्स्ट की भविष्यवाणी करने के अलावा कुछ नहीं कर रहा है, और कभी-कभी संभावित टेक्स्ट कोड बाड़ है, या डेटा से पहले एक दोस्ताना "ठीक है, यहाँ जाओ!", या इसके बाद एक अनुगामी टिप्पणी है। इनमें से कोई भी JSON को पढ़ने वाले कोड को तोड़ देता है। दया से पूछना JSON की ओर भविष्यवाणी को पूर्वाग्रह करता है; यह इसे मजबूर नहीं करता है।
तो प्रदाता आपको एक असली लीवर देते हैं। response_format के साथ JSON मोड चालू करना सिर्फ पूछने से अधिक करता है: यह मॉडल को सीमित करता है ताकि सिंटैक्स मान्य JSON के रूप में वापस आए।
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"]) # "Oslo" 18response_format={"type": "json_object"} आउटपुट को मान्य JSON सिंटैक्स बनाता है, इसलिए json.loads भटकते हुए गद्य पर चोक नहीं करेगा। आप अभी भी प्रॉम्प्ट में चाहने वाली फ़ील्ड का वर्णन करते हैं, क्योंकि JSON मोड मान्य JSON का वादा करता है, आपके ध्यान में रखी गई विशेष फ़ील्ड को नहीं। यह "बिल्कुल JSON नहीं" को नियम से बाहर करता है, "गलत आकार के साथ JSON" को नहीं। एक चीज जो यह अभी भी वादा नहीं कर सकता है: यदि उत्तर आधे रास्ते में काट दिया जाता है, तो आप एक आधा JSON ऑब्जेक्ट प्राप्त कर सकते हैं, इसलिए यह विश्वसनीय सिंटैक्स है, हर मामले में एक कठोर गारंटी नहीं है।
response_format को JSON मोड पर सेट करना सिंटैक्स को बाध्य करता है ताकि यह मान्य JSON के रूप में वापस आए। यह वादा नहीं करता है कि फ़ील्ड मेल खाते हैं जो आपने पूछा है, और आधे रास्ते में कटा हुआ उत्तर अभी भी आधा ऑब्जेक्ट आ सकता है, इसलिए प्रॉम्प्ट में आकार का वर्णन करें और जांचें कि क्या होता है। स्कीमा के साथ आकार को लॉक करें
JSON मोड सिंटैक्स को रखता है, लेकिन मॉडल अभी भी किसी फ़ील्ड को पुनः नाम दे सकता है, छोड़ सकता है, या चीजों को अलग तरीके से नेस्ट कर सकता है। उस विगलरूम को हटाने के लिए, इसे एक स्कीमा दें: फ़ील्ड और प्रकारों का सटीक विवरण जो आउटपुट को रखना चाहिए, एक फॉर्म जो मॉडल को भरना आवश्यक है। strict: true के साथ, आउटपुट को फिर उस फॉर्म से मेल खाने की गारंटी दी जाती है।
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": "Mara Lin", "email": "[email protected]"}एक स्कीमा कैसे कठोर गारंटी हो सकता है जब मॉडल केवल टोकन की भविष्यवाणी करता है? क्योंकि प्रदाता यह सीमित करता है कि कौन से टोकन की भविष्यवाणी की जा सकती है। प्रत्येक चरण पर मॉडल अभी भी सभी संभावित अगले टोकन को रैंक करता है, लेकिन सिस्टम उन लोगों को हटा देता है जो आपकी स्कीमा को तोड़ेंगे, और मॉडल जो बचता है उससे चुनता है।
यदि स्कीमा कहता है कि अगली फ़ील्ड email होनी चाहिए, तो जो भी टोकन किसी अन्य फ़ील्ड को शुरू करेंगे वह मॉडल चुनने से पहले तालिका से हटा दिए जाते हैं। ऑफ-शेप टोकन हटा दिए जाते हैं, इसलिए मॉडल ऑफ-शेप से दूर नहीं हो सकता।
एक नरम प्रभाव जानने योग्य है। आप जो फ़ील्ड नाम चुनते हैं वह मॉडल को पढ़ने में मदद करता है जब मूल्य की भविष्यवाणी करता है, इसलिए एक स्पष्ट नाम बेहतर उत्तर का मार्गदर्शन करता है। tempC भरने के लिए कहा जाता है, मॉडल सेल्सियस संख्या की ओर झुकता है; value भरने के लिए पूछा जाता है, इसके पास बहुत कुछ नहीं है। फ़ील्ड को स्पष्ट रूप से नाम देना आंशिक निर्देश, आंशिक स्कीमा है।
strict: true के साथ इसे सटीक फ़ील्ड और प्रकार मिलते हैं, क्योंकि सिस्टम मॉडल चुनने से पहले कोई भी टोकन हटा देता है जो आकार को तोड़ेगा। वह प्रवर्तन है, एक विनम्र अनुरोध नहीं। अपनी फ़ील्ड को स्पष्ट रूप से भी नाम दें, क्योंकि फ़ील्ड नाम संदर्भ है जो मॉडल पढ़ता है जब यह मूल्य की भविष्यवाणी करता है। मुझे यह विश्वास करने में समय लगा कि `tempC` जैसा नाम असली काम करता है। फिर भी मान्य करें
एक स्कीमा आकार को सीमित करता है, लेकिन आउटपुट को बाहरी दुनिया से डेटा मानते हैं। आकार मान्य हो सकता है जबकि सामग्री गलत हो सकती है: निकाला गया ईमेल जो वास्तव में एक टाइपो है, एक संख्या जो मॉडल ने अनुमान लगाई है, एक फ़ील्ड खाली छोड़ दी गई क्योंकि इनपुट इसमें नहीं था। और कॉल अभी भी साधारण तरीके से विफल हो सकता है, जैसे max_tokens से कट जाना और ट्रंकेटेड JSON के रूप में आना। रक्षात्मक रूप से पार्स करें: एक मान्य आकार सही मान नहीं है।
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 साथ-साथ मान जांच सस्ता बीमा है उस जवाब के खिलाफ जो अच्छी तरह से गठित है और अभी भी गलत है।
try/except में लपेटें और विश्वास से पहले मान जांचें। तय करें कि सत्यापन विफल होने पर क्या होता है, पुनः प्रयास या दोस्ताना त्रुटि, इसलिए यह बाद में एक हड़बड़ी नहीं है। दो पैटर्न: वर्गीकृत और निष्कर्षण
अधिकांश संरचित-आउटपुट कार्य दो आकारों में से एक है।
वर्गीकरण एक इनपुट को लेबल के एक निश्चित सेट में से एक में बांटता है। स्कीमा में एक enum आउटपुट को बिल्कुल उन लेबलों तक सीमित करता है, एक ही टोकन-प्रूनिंग ट्रिक का उपयोग करके: लेबल स्थिति पर, केवल अनुमत मानों की भविष्यवाणी की जा सकती है, इसलिए मॉडल एक श्रेणी का आविष्कार नहीं कर सकता है जो आपकी सूची में नहीं है।
# वर्गीकरण के लिए स्कीमा टुकड़ा
{"type": "string", "enum": ["billing", "technical", "general"]}निष्कर्षण मुक्त पाठ से विशिष्ट फ़ील्ड खींचता है, जैसे पहले का संपर्क उदाहरण: एक नाम, एक तारीख, एक राशि, उत्पाद नाम की सूची। एक साथ, वर्गीकृत और निष्कर्षण वास्तविक AI सुविधाओं का एक बड़ा हिस्सा कवर करते हैं: समर्थन टिकट रूटिंग, सामग्री टैगिंग, ईमेल को रिकॉर्ड में बदलना, रसीद पढ़ना। दोनों एक अस्पष्ट टेक्स्ट जेनरेटर को एक विश्वसनीय घटक में बदल देते हैं जो आपका कोड बना सकता है।
व्यवहार में
एक अस्पष्ट समर्थन ईमेल को एक संरचित टिकट में बदलना, एक स्कीमा में वर्गीकरण और निष्कर्षण को जोड़ते हुए:
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": "पिछले महीने की सदस्यता के लिए दोहरे शुल्क।"}एक कॉल ईमेल को दो तरीकों से वर्गीकृत करता है और एक सारांश निकालता है, एक रिकॉर्ड लौटाता है जिसे आपका कोड मार्ग और स्टोर कर सकता है, आकार की गारंटी के साथ और मान अभी भी जांच के लायक हैं। अब तक सबकुछ टेक्स्ट में बहा है और टेक्स्ट से बाहर। अगला आप मॉडल को एक अलग भावना देते हैं: एम्बेडिंग में, टेक्स्ट संख्या बन जाता है जिसे आप अर्थ से तुलना कर सकते हैं, खोज और अपने स्वयं के दस्तावेजों के साथ काम करने की नींव।

