بزرگترین چالش اتصال مدلهای زبانی به نرمافزارهای تجاری، دریافت متنی آزاد بهجای دادههای ساختیافته و معتبر است. اتکا به پرامپتهایی مثل «پاسخ را در قالب JSON بده» معمولاً پایپلاینهای پروداکشن را به علت خطاهای نحوی، کدهای مارکداون اضافی یا مقادیر نامعتبر متوقف میکند. در این درس یاد میگیرید چگونه بهجای سروکلهزدن با عبارات باقاعده (Regex)، دادههای خروجی را مستقیماً در سطح دیکودینگ مقید و نوعدار کنید.
اگر برای ارسال درخواست به ارائهدهندگان خارجی مانند OpenAI یا Anthropic با چالش پرداختهای ارزی یا محدودیت دسترسی بر اساس نشانی اینترنتی (IP) مواجه هستید، پیادهسازی این الگوها تفاوتی نخواهد داشت؛ چرا که کتابخانههایی مثل Instructor و پروتکلهای دیکود مقید روی تمام گیتویهای واسط داخلی که با فرمت استاندارد OpenAI سازگارند، تنها با تغییر آدرس پایه (Base URL) و کلید دسترسی کار میکنند.
در پایان این درس، ساخت لایههای اعتبارسنجی با Pydantic، پیادهسازی دیکود مقید از پایه و سازوکار تلاش مجدد خودکار را میآموزید تا بتوانید متنهای ساختارنیافته را با دقت صددرصدی به مدلهای دادهی معتبر پایتون تبدیل کنید.
مدل زبانی شما یک رشته برمیگرداند؛ اما برنامهی شما به JSON نیاز دارد. همین فاصلهی بهظاهر ساده، بیشتر از هر توهمی در مدلها، سیستمهای پروداکشن را زمین زده است. خروجی ساختاریافته (Structured output) پلی میان زبان طبیعی و دادههای نوعدار (Typed data) است. اگر آن را درست پیاده کنید، مدل زبانی شما به یک API مطمئن تبدیل میشود؛ وگرنه ساعت 3 نصفشب باید مشغول سروکله زدن با Regex برای بیرون کشیدن داده از دل متن آزاد باشید.
مرتبط: بخش 5 · 20 (خروجیهای ساختاریافته و دیکود مقید) مبانی نظری سطح دیکودر (پردازندههای لاجیت مبتنی بر FSM/CFG، کتابخانهی Outlines و XGrammar) را پوشش میدهد. تمرکز این درس روی پوستهی SDKهای پروداکشن (قابلیت response_format در OpenAI، فراخوانی ابزار در Anthropic و کتابخانهی Instructor) است — اگر میخواهید بدانید در لایههای زیرین API چه میگذرد، ابتدا بخش 5 · 20 را بخوانید.
اهداف یادگیری
- پیادهسازی حالت JSON و خروجیهای مقید به اسکیما با استفاده از پارامترهای API در OpenAI و Anthropic
- ساخت لایهی اعتبارسنجی با Pydantic برای رد کردن خروجیهای بدشکل مدل و تلاش مجدد همراه با بازخورد خطا
- توضیح نحوهی تحمیل خروجی معتبر JSON در سطح توکن بهکمک دیکود مقید (Constrained decoding) بدون نیاز به پسپردازش
- طراحی پرامپتهای استخراج دادهی مستحکم برای تبدیل مطمئن متن ساختارنیافته به ساختارهای دادهی نوعدار
صورت مسئله
از مدل زبانی میخواهید: «نام محصول، قیمت و وضعیت موجودی را از این متن استخراج کن.» مدل اینطور پاسخ میدهد:
The product is the Sony WH-1000XM5 headphones, which cost $348.00 and are currently in stock.
این پاسخ کاملاً درست است؛ اما برای برنامهی شما کمترین فایدهای ندارد. سیستم انبارداری شما به دادهای مثل {"product": "Sony WH-1000XM5", "price": 348.00, "in_stock": true} نیاز دارد. شما یک شیء JSON با کلیدهای مشخص، انواع دادهای دقیق و قیدهای معین روی مقادیر میخواهید، نه یک جمله.
راهحل سادهلوحانه این است: عبارت «در قالب JSON پاسخ بده» را به پرامپت اضافه کنید. این روش در 90% مواقع جواب میدهد. در 10% باقیمانده، مدل خروجی JSON را داخل بلوکهای کد مارکداون میپیچد، یا متنی مقدماتی مثل «این هم JSON شما:» میآورد، یا با بستن زودهنگام یک براکت، خروجی JSON با ساختار نحوی نامعتبر تحویل میدهد. در نتیجه، پارسر JSON کرش میکند و پایپلاین از کار میافتد. برای حل این مشکل، بلوک try/except و حلقهی تلاش مجدد اضافه میکنید؛ اما تلاش مجدد گاهی دادههای متفاوتی برمیگرداند. حالا علاوه بر مشکل پارس کردن، با مشکل ناسازگاری دادهها هم روبهرو هستید.
این مسئله ربطی به مهندسی پرامپت ندارد؛ این یک مشکل در فرآیند دیکود است. مدل توکنها را از چپ به راست تولید میکند. در هر جایگاه، محتملترین توکن بعدی را از بین دایرهی واژگانی با بیش از 100 هزار گزینه برمیگزیند. در هر جایگاه مشخص، بیشتر این گزینهها خروجی JSON را نامعتبر میکنند. اگر مدل بهتازگی توکن {"price": را تولید کرده باشد، توکن بعدی حتماً باید یک رقم، علامت نقلقول (برای رشته)، null، true، false یا علامت منفی باشد. هر چیز دیگری ساختار نحوی JSON را خراب میکند. بدون قید، ممکن است مدل یک کلمهی انگلیسی کاملاً منطقی را انتخاب کند که از نظر ساختار گرامری، خروجی را به فاجعه بکشاند.
مفاهیم پایه
طیف خروجیهای ساختاریافته
برای کنترل خروجی ساختاریافته چهار سطح وجود دارد که قابلیت اطمینان هر یک از سطح قبلی بیشتر است.
مبتنی بر پرامپت («در قالب JSON معتبر پاسخ بده»): هیچ اجباری در کار نیست. مدل معمولاً رعایت میکند، اما گاهی هم نه. قابلیت اطمینان: حدود 90%. حالتهای شکست: بلوکهای مارکداون، متن مقدماتی، خروجی نصفهکاره، ساختار اشتباه.
حالت JSON: در این حالت، API تضمین میکند که خروجی حتماً یک JSON معتبر است. پارامتر response_format: { type: "json_object" } در OpenAI این قابلیت را فعال میکند. خروجی بدون خطا پارس میشود؛ اما شاید با اسکیمای مورد انتظار شما همخوانی نداشته باشد — کلیدهای اضافی داشته باشد، نوع دادهها اشتباه باشد یا فیلدهایی جا بیفتند.
حالت اسکیما: در این حالت، API یک JSON Schema دریافت میکند و تضمین میدهد خروجی دقیقاً با آن مطابقت دارد. در سال 2026 همهی ارائهدهندگان مطرح بهطور پیشفرض از این قابلیت پشتیبانی میکنند: قابلیت response_format: { type: "json_schema", json_schema: {...} } (یا با tool_choice="required") در OpenAI، فراخوانی ابزار همراه با input_schema در Anthropic، و response_schema در کنار response_mime_type: "application/json" در Gemini. خروجی دقیقاً با همان کلیدها، انواع داده و قیدهای تعیینشده به دست میآید.
دیکود مقید: هنگام تولید هر توکن در فرآیند دیکود، تمام توکنهایی که باعث نامعتبر شدن خروجی میشوند ماسک (احتمالشان صفر) میشوند. اگر اسکیما عدد بخواهد و مدل در آستانهی تولید یک حرف باشد، احتمال آن توکن صفر میشود. مدل تنها مجاز به تولید توکنهایی است که به یک خروجی معتبر ختم شوند. این همان مکانیزمی است که در حالت خروجی ساختاریافتهی OpenAI و کتابخانههایی مثل Outlines و Guidance در لایههای زیرین پیاده شده است.
زبان قرارداد: JSON Schema
با استفاده از JSON Schema به مدل (یا لایهی اعتبارسنجی) اعلام میکنید که خروجی نهایی باید چه ساختاری داشته باشد. همهی سیستمهای مطرح خروجی ساختاریافته از این استاندارد استفاده میکنند.
{
"type": "object",
"properties": {
"product": { "type": "string" },
"price": { "type": "number", "minimum": 0 },
"in_stock": { "type": "boolean" },
"categories": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["product", "price", "in_stock"]
}
این اسکیما تعیین میکند: خروجی باید شیئی شامل فیلد رشتهای product، فیلد عددی غیرمنفی price، فیلد بولی in_stock و آرایهای اختیاری از رشتهها برای categories باشد. هر خروجی مغایر با این مشخصات رد میشود.
اسکیماها حالتهای دشوار را مدیریت میکنند: اشیای تودرتو، آرایههایی با اعضای نوعدار، مقادیر گزینشی یا اِنام (محدود کردن رشته به مقادیری خاص)، انطباق الگو (Regex روی رشتهها) و ترکیبکنندهها (oneOf، anyOf، allOf برای خروجیهای چندشکلی).
الگوی Pydantic
در پایتون معمولاً JSON Schema را دستی نمینویسید. کافی است یک مدل Pydantic تعریف کنید تا اسکیما را برایتان بسازد.
from pydantic import BaseModel
class Product(BaseModel):
product: str
price: float
in_stock: bool
categories: list[str] = []
این کد دقیقاً همان JSON Schema بالا را میسازد. کتابخانهی Instructor (و SDK شرکت OpenAI) مدلهای Pydantic را مستقیم دریافت میکنند: کلاس مدل را به متد میدهید و یک نمونهی معتبر و نوعدار تحویل میگیرید. اگر خروجی مدل زبانی تطابق نداشته باشد، Instructor بهطور خودکار درخواست را تکرار میکند.
فراخوانی تابع / استفاده از ابزار
رویکرد دیگری برای حل همین مسئله است. بهجای اینکه مستقیماً از مدل JSON بخواهید، «ابزارها» (توابع) را با پارامترهای نوعدار برای آن تعریف میکنید. مدل یک فراخوانی ابزار (Tool call) شامل آرگومانهای ساختاریافته تحویل میدهد. OpenAI به این سازوکار Function Calling میگوید و Anthropic از اصطلاح Tool Use استفاده میکند. نتیجه در هر دو حالت یکسان است: دادهی ساختاریافته.
زمانی که مدل باید علاوه بر پر کردن پارامترها، تابع مناسب را هم انتخاب کند، استفاده از ابزار گزینهی بهتری است. اگر 10 اسکیمای استخراج مختلف دارید و مدل باید بر اساس متن ورودی گزینهی درست را برگزیند، ابزارها هم کار انتخاب اسکیما و هم تولید خروجی ساختاریافته را همزمان برایتان انجام میدهند.
حالتهای شکست رایج
حتی با اعمال سفتوسخت اسکیما، خروجیهای ساختاریافته ممکن است دچار خطاهای ظریفی شوند.
تولید مقادیر خیالی (توهم): خروجی کاملاً با اسکیما مطابقت دارد، اما دادههای مندرآوردی دارد. مثلاً در متن قیمت 348 دلار ذکر شده، اما مدل خروجی {"price": 299.99} را تحویل میدهد. اعتبارسنجی اسکیما متوجه این موضوع نمیشود؛ چون نوع داده درست است ولی مقدار آن غلط است.
سردرگمی در Enum: اگر یک فیلد را به مقادیر ["in_stock", "out_of_stock", "preorder"] محدود کرده باشید، ممکن است مدل مقداری مثل "available" برگرداند؛ مقداری که از نظر معنایی درست است اما در مجموعهی مقادیر مجاز نیست. دیکود مقید مناسب جلوی این خطا را میگیرد، در حالی که روشهای مبتنی بر پرامپت از پس آن برنمیآیند.
عمق اشیای تودرتو: اسکیماهای عمیق (4 لایه یا بیشتر) خطاهای بیشتری تولید میکنند. در هر لایهی تودرتو، احتمال اینکه مدل رشتهی ساختار را گم کند بیشتر میشود.
طول آرایهها: مدل ممکن است تعداد عناصر آرایه را کمتر یا بیشتر از حد مجاز تولید کند. اسکیماها از محدودیتهای minItems و maxItems پشتیبانی میکنند، اما همهی ارائهدهندهها این قیدها را در سطح دیکود اعمال نمیکنند.
حذف فیلدهای اختیاری: گاهی مدل فیلدهایی را که از نظر فنی اختیاری هستند، اما در سناریوی شما اهمیت معنایی دارند، حذف میکند. حتی اگر دادهها گاهی موجود نیستند، آنها را در اسکیما اجباری تعریف کنید تا مدل وادار شود در صورت نبود داده، صراحتاً null تولید کند.
پیادهسازی از صفر
گام 1: اعتبارسنج اسکیماهای JSON
اعتبارسنجی از پایه بسازید که بررسی کند آیا یک شیء پایتونی با یک JSON Schema مطابقت دارد یا نه. این همان کدی است که در سمت خروجی اجرا میشود تا مطابقت را تأیید کند.
import json
def validate_schema(data, schema):
errors = []
_validate(data, schema, "", errors)
return errors
def _validate(data, schema, path, errors):
schema_type = schema.get("type")
if schema_type == "object":
if not isinstance(data, dict):
errors.append(f"{path}: expected object, got {type(data).__name__}")
return
for key in schema.get("required", []):
if key not in data:
errors.append(f"{path}.{key}: required field missing")
properties = schema.get("properties", {})
for key, value in data.items():
if key in properties:
_validate(value, properties[key], f"{path}.{key}", errors)
elif schema_type == "array":
if not isinstance(data, list):
errors.append(f"{path}: expected array, got {type(data).__name__}")
return
min_items = schema.get("minItems", 0)
max_items = schema.get("maxItems", float("inf"))
if len(data) < min_items:
errors.append(f"{path}: array has {len(data)} items, minimum is {min_items}")
if len(data) > max_items:
errors.append(f"{path}: array has {len(data)} items, maximum is {max_items}")
items_schema = schema.get("items", {})
for i, item in enumerate(data):
_validate(item, items_schema, f"{path}[{i}]", errors)
elif schema_type == "string":
if not isinstance(data, str):
errors.append(f"{path}: expected string, got {type(data).__name__}")
return
enum_values = schema.get("enum")
if enum_values and data not in enum_values:
errors.append(f"{path}: '{data}' not in allowed values {enum_values}")
elif schema_type == "number":
if not isinstance(data, (int, float)):
errors.append(f"{path}: expected number, got {type(data).__name__}")
return
minimum = schema.get("minimum")
maximum = schema.get("maximum")
if minimum is not None and data < minimum:
errors.append(f"{path}: {data} is less than minimum {minimum}")
if maximum is not None and data > maximum:
errors.append(f"{path}: {data} is greater than maximum {maximum}")
elif schema_type == "boolean":
if not isinstance(data, bool):
errors.append(f"{path}: expected boolean, got {type(data).__name__}")
elif schema_type == "integer":
if not isinstance(data, int) or isinstance(data, bool):
errors.append(f"{path}: expected integer, got {type(data).__name__}")
گام 2: تبدیل مدل شبیه Pydantic به اسکیما
مبدل سادهای برای تبدیل کلاس به اسکیما بسازید تا با تعریف یک کلاس پایتون، JSON Schema آن بهطور خودکار ساخته شود.
class SchemaField:
def __init__(self, field_type, required=True, default=None, enum=None, minimum=None, maximum=None):
self.field_type = field_type
self.required = required
self.default = default
self.enum = enum
self.minimum = minimum
self.maximum = maximum
def python_type_to_schema(field):
type_map = {
str: "string",
int: "integer",
float: "number",
bool: "boolean",
}
schema = {}
if field.field_type in type_map:
schema["type"] = type_map[field.field_type]
elif field.field_type == list:
schema["type"] = "array"
schema["items"] = {"type": "string"}
elif isinstance(field.field_type, dict):
schema = field.field_type
if field.enum:
schema["enum"] = field.enum
if field.minimum is not None:
schema["minimum"] = field.minimum
if field.maximum is not None:
schema["maximum"] = field.maximum
return schema
def model_to_schema(name, fields):
properties = {}
required = []
for field_name, field in fields.items():
properties[field_name] = python_type_to_schema(field)
if field.required:
required.append(field_name)
return {
"type": "object",
"properties": properties,
"required": required,
}
گام 3: فیلتر کردن توکنها با دیکود مقید
دیکود مقید را شبیهسازی کنید. با در دست داشتن یک رشتهی ناقص JSON و یک اسکیما، مشخص کنید چه دستههایی از توکنها در جایگاه فعلی مجاز به تولید هستند.
def next_valid_tokens(partial_json, schema):
stripped = partial_json.strip()
if not stripped:
return ["{"]
try:
json.loads(stripped)
return ["<EOS>"]
except json.JSONDecodeError:
pass
last_char = stripped[-1] if stripped else ""
if last_char == "{":
return ['"', "}"]
elif last_char == '"':
if stripped.endswith('":'):
return ['"', "0-9", "true", "false", "null", "[", "{"]
return ["a-z", '"']
elif last_char == ":":
return [" ", '"', "0-9", "true", "false", "null", "[", "{"]
elif last_char == ",":
return [" ", '"', "{", "["]
elif last_char in "0123456789":
return ["0-9", ".", ",", "}", "]"]
elif last_char == "}":
return [",", "}", "]", "<EOS>"]
elif last_char == "]":
return [",", "}", "<EOS>"]
elif last_char == "[":
return ['"', "0-9", "true", "false", "null", "{", "[", "]"]
else:
return ["any"]
def demonstrate_constrained_decoding():
partial_states = [
'',
'{',
'{"product"',
'{"product":',
'{"product": "Sony"',
'{"product": "Sony",',
'{"product": "Sony", "price":',
'{"product": "Sony", "price": 348',
'{"product": "Sony", "price": 348}',
]
print(f"{'Partial JSON':<45} {'Valid Next Tokens'}")
print("-" * 80)
for state in partial_states:
valid = next_valid_tokens(state, {})
display = state if state else "(empty)"
print(f"{display:<45} {valid}")
گام 4: پایپلاین استخراج داده
همهچیز را در یک پایپلاین استخراج ترکیب کنید: اسکیما را مشخص کنید، تولید خروجی ساختاریافته توسط مدل را شبیهسازی کنید، اعتبارسنجی را انجام دهید و منطق تلاش مجدد را مدیریت کنید.
def simulate_llm_extraction(text, schema, attempt=0):
if "headphones" in text.lower() or "sony" in text.lower():
if attempt == 0:
return '{"product": "Sony WH-1000XM5", "price": 348.00, "in_stock": true, "categories": ["audio", "headphones"]}'
return '{"product": "Sony WH-1000XM5", "price": 348.00, "in_stock": true}'
if "laptop" in text.lower():
return '{"product": "MacBook Pro 16", "price": 2499.00, "in_stock": false, "categories": ["computers"]}'
return '{"product": "Unknown", "price": 0, "in_stock": false}'
def extract_with_retry(text, schema, max_retries=3):
for attempt in range(max_retries):
raw = simulate_llm_extraction(text, schema, attempt)
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f" Attempt {attempt + 1}: JSON parse error -- {e}")
continue
errors = validate_schema(data, schema)
if not errors:
return data
print(f" Attempt {attempt + 1}: Schema validation errors -- {errors}")
return None
product_schema = {
"type": "object",
"properties": {
"product": {"type": "string"},
"price": {"type": "number", "minimum": 0},
"in_stock": {"type": "boolean"},
"categories": {"type": "array", "items": {"type": "string"}},
},
"required": ["product", "price", "in_stock"],
}
گام 5: اجرای پایپلاین کامل
def run_demo():
print("=" * 60)
print(" Structured Output Pipeline Demo")
print("=" * 60)
print("\n--- Schema Definition ---")
product_fields = {
"product": SchemaField(str),
"price": SchemaField(float, minimum=0),
"in_stock": SchemaField(bool),
"categories": SchemaField(list, required=False),
}
generated_schema = model_to_schema("Product", product_fields)
print(json.dumps(generated_schema, indent=2))
print("\n--- Schema Validation ---")
test_cases = [
({"product": "Test", "price": 10.0, "in_stock": True}, "Valid object"),
({"product": "Test", "price": -5.0, "in_stock": True}, "Negative price"),
({"product": "Test", "in_stock": True}, "Missing price"),
({"product": "Test", "price": "ten", "in_stock": True}, "String as price"),
("not an object", "String instead of object"),
]
for data, label in test_cases:
errors = validate_schema(data, product_schema)
status = "PASS" if not errors else f"FAIL: {errors}"
print(f" {label}: {status}")
print("\n--- Constrained Decoding Simulation ---")
demonstrate_constrained_decoding()
print("\n--- Extraction Pipeline ---")
texts = [
"The Sony WH-1000XM5 headphones are priced at $348 and currently available.",
"The new MacBook Pro 16-inch laptop costs $2499 but is sold out.",
"This is a random sentence with no product info.",
]
for text in texts:
print(f"\n Input: {text[:60]}...")
result = extract_with_retry(text, product_schema)
if result:
print(f" Output: {json.dumps(result)}")
else:
print(f" Output: FAILED after retries")
استفاده در عمل
خروجیهای ساختاریافته در OpenAI
# from openai import OpenAI
# from pydantic import BaseModel
#
# client = OpenAI()
#
# class Product(BaseModel):
# product: str
# price: float
# in_stock: bool
#
# response = client.beta.chat.completions.parse(
# model="gpt-5-mini",
# messages=[
# {"role": "system", "content": "Extract product information."},
# {"role": "user", "content": "Sony WH-1000XM5, $348, in stock"},
# ],
# response_format=Product,
# )
#
# product = response.choices[0].message.parsed
# print(product.product, product.price, product.in_stock)
قابلیت خروجیهای ساختاریافته در OpenAI در لایههای درونی خود از دیکود مقید بهره میبرد. تولید تکتک توکنها توسط مدل تضمین میکند که خروجی نهایی حتماً با اسکیمای Pydantic تطابق دارد. نه نیازی به تلاش مجدد است و نه نیازی به اعتبارسنجی؛ قیدها در تاروپود فرایند دیکود جا گرفتهاند.
فراخوانی ابزار در Anthropic
# import anthropic
#
# client = anthropic.Anthropic()
#
# response = client.messages.create(
# model="claude-opus-4-7",
# max_tokens=1024,
# tools=[{
# "name": "extract_product",
# "description": "استخراج اطلاعات محصول از متن",
# "input_schema": {
# "type": "object",
# "properties": {
# "product": {"type": "string"},
# "price": {"type": "number"},
# "in_stock": {"type": "boolean"},
# },
# "required": ["product", "price", "in_stock"],
# },
# }],
# messages=[{"role": "user", "content": "Extract: Sony WH-1000XM5, $348, in stock"}],
# )
سرویس Anthropic خروجی ساختاریافته را از طریق فراخوانی ابزار فراهم میکند. مدل یک فراخوانی ابزار با آرگومانهای ساختاریافته ارسال میکند که دقیقاً با input_schema همخوانی دارد. نتیجه یکی است، فقط ظاهر کار با API تفاوت دارد.
کتابخانهی Instructor
# pip install instructor
# import instructor
# from openai import OpenAI
# from pydantic import BaseModel
#
# client = instructor.from_openai(OpenAI())
#
# class Product(BaseModel):
# product: str
# price: float
# in_stock: bool
#
# product = client.chat.completions.create(
# model="gpt-5-mini",
# response_model=Product,
# messages=[{"role": "user", "content": "Sony WH-1000XM5, $348, in stock"}],
# )
کتابخانهی Instructor کلاینت مدلهای زبانی را در بر میگیرد و قابلیت تلاش مجدد خودکار همراه با اعتبارسنجی را به آنها اضافه میکند. اگر اولین تلاش در اعتبارسنجی رد شود، خطاها را بهعنوان بافت کمکی برای مدل میفرستد تا خروجی را اصلاح کند. این کتابخانه محدود به OpenAI نیست و با هر ارائهدهندهای کار میکند.
خروجی این درس
از این درس فایل outputs/prompt-structured-extractor.md حاصل میشود؛ یک قالب پرامپت (Prompt template) با قابلیت استفادهی مجدد که با دریافت هر متن و ساختار اسکیما، دادههای ساختاریافته را استخراج میکند. کافی است یک JSON Schema و متن ساختارنیافته به آن بدهید تا JSON اعتبارسنجیشده تحویل بگیرید.
همچنین فایل outputs/skill-structured-outputs.md را خواهید داشت؛ یک چارچوب تصمیمگیری برای انتخاب راهبرد مناسب خروجی ساختاریافته متناسب با ارائهدهنده، سطح اطمینان مورد نیاز و پیچیدگی اسکیما.
تمرینها
-
اعتبارسنج اسکیما را گسترش دهید تا از
oneOfهم پشتیبانی کند (داده باید دقیقاً با یکی از چند اسکیما همخوانی داشته باشد). با این قابلیت میتوانید خروجیهای چندشکلی را مدیریت کنید؛ مثلاً فیلدی که میتواند شیئی از نوعProductیاServiceبا ساختارهای متفاوت باشد. -
ابزاری برای مقایسهی اسکیماها («Schema diff») بسازید که دو اسکیما را مقایسه کند و تغییرات شکننده (مثل حذف فیلدهای اجباری، تغییر نوع دادهها) را از تغییرات غیرشکننده (مثل اضافه کردن فیلدهای اختیاری، سبکتر کردن قیدها) تشخیص دهد. این ابزار برای نسخهبندی اسکیماهای استخراج در محیط پروداکشن حیاتی است.
-
شبیهساز واقعیتری برای دیکود مقید بنویسید. با داشتن یک JSON Schema و دایرهی واژگانی با 100 توکن (حروف، ارقام، نشانهگذاری، کلمات کلیدی)، مراحل تولید را گامبهگام پیش ببرید و توکنهای نامعتبر را در هر جایگاه ماسک کنید. محاسبه کنید در هر گام چند درصد از دایرهی واژگان مجاز به انتخاب هستند.
-
یک مجموعهی ارزیابی برای استخراج داده طراحی کنید. 50 متن توصیف محصول به همراه خروجی دستی برچسبگذاریشده در قالب JSON آماده کنید. پایپلاین استخراج را روی هر 50 نمونه اجرا کنید و تطابق دقیق، دقت در سطح فیلدها و رعایت انواع داده را بسنجید. مشخص کنید استخراج کدام فیلدها برای سیستم سختتر است.
-
به پایپلاین استخراج خود «امتیاز اطمینان» اضافه کنید. برای هر فیلد استخراجشده، برآورد کنید مدل چقدر به پاسخ خود اطمینان دارد (این کار را بر اساس احتمال توکنها یا با 3 بار اجرای استخراج و سنجش سازگاری نتایج انجام دهید). فیلدهایی را که امتیاز پایینی دارند برای بازبینی انسانی علامت بگذارید.
اصطلاحات کلیدی
| اصطلاح | در گفتوگوهای روزمره | معنای واقعی و فنی |
|---|---|---|
| حالت JSON (JSON mode) | «JSON برمیگرداند» | فلگی در API که معتبر بودن ساختار نحوی خروجی JSON را تضمین میکند، اما هیچ اسکیمای خاصی را اعمال نمیکند |
| خروجی ساختاریافته (Structured output) | «JSON نوعدار» | خروجی دادهای که دقیقاً با یک JSON Schema مشخص با کلیدها، انواع داده و قیدهای معین مطابقت دارد |
| دیکود مقید (Constrained decoding) | «تولید هدایتشده» | ماسک کردن توکنهایی که در هر جایگاه تولید باعث نامعتبر شدن خروجی میشوند — تضمین تطابق 100 درصدی با اسکیما |
| اسکیما JSON (JSON Schema) | «یک قالب برای JSON» | زبانی توصیفی برای مشخص کردن ساختار، انواع داده و قیدهای دادههای JSON (مورد استفاده در OpenAPI، فرمهای JSON و غیره) |
| کتابخانهی Pydantic | «دیتایکلاسهای پیشرفته در پایتون» | کتابخانهای پایتونی برای تعریف مدلهای داده همراه با اعتبارسنجی نوع، مورد استفاده در FastAPI و Instructor برای ساخت خودکار JSON Schema |
| فراخوانی تابع (Function calling) | «استفاده از ابزار» | امکانی که مدل زبانی بهجای متن آزاد، یک فراخوانی ساختاریافتهی تابع (نام به همراه آرگومانهای نوعدار) تولید میکند — OpenAI و Anthropic هر دو از آن پشتیبانی میکنند |
| کتابخانهی Instructor | «Pydantic برای مدلهای زبانی» | کتابخانهای در پایتون که کلاینتهای مدل زبانی را پوشش میدهد تا نمونههای اعتبارسنجیشدهی Pydantic تحویل دهند و در صورت شکست اعتبارسنجی، فرآیند را بهطور خودکار تکرار میکند |
| ماسک کردن توکن (Token masking) | «فیلتر کردن دایره واژگان» | صفر کردن احتمال توکنهای نامناسب در جریان تولید تا مدل نتواند آنها را انتخاب کند |
| تطابق با اسکیما (Schema compliance) | «درست بودن ساختار داده» | یعنی خروجی همهی فیلدهای اجباری را دارد، نوع دادهها درست است، مقادیر در چارچوب قیدها هستند و هیچ فیلد غیرمجازی وجود ندارد |
| حلقهی تلاش مجدد (Retry loop) | «تکرار کن تا درست شود» | فرستادن خطاهای اعتبارسنجی به مدل برای اصلاح خروجی — Instructor این فرآیند را خودکار و تا سقفی معین انجام میدهد |
برای مطالعهی بیشتر
- راهنمای خروجیهای ساختاریافته در OpenAI -- مستندات رسمی برای دیکود مقید مبتنی بر JSON Schema در API شرکت OpenAI
- Willard & Louf, 2023 -- "Efficient Guided Generation for Large Language Models" -- مقالهی معرفی Outlines که نحوهی کامپایل JSON Schema به ماشینهای حالت متناهی برای اعمال قید در سطح توکن را تشریح میکند
- مستندات کتابخانهی Instructor -- کتابخانهی استاندارد دریافت خروجی ساختاریافته از هر مدل زبانی بهکمک اعتبارسنجی Pydantic و تلاش مجدد
- راهنمای فراخوانی ابزار در Anthropic -- نحوهی پیادهسازی خروجی ساختاریافته در Claude با کمک ابزارها و input_schema مبتنی بر اسکیما
- مشخصات استاندارد JSON Schema -- مشخصات کامل زبان اسکیما که در همهی سیستمهای مطرح تولید خروجی ساختاریافته استفاده میشود
- کتابخانهی Outlines -- ابزاری متنباز برای تولید مقید با بهرهگیری از Regex و JSON Schema کامپایلشده به ماشینهای حالت متناهی
- Dong et al., "XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models" (MLSys 2025) -- پیشرفتهترین موتور گرامری حال حاضر؛ کامپایل اتوماتای پشتهای (Pushdown automaton) که ماسک کردن توکنها را با سرعت حدود 100 نانوثانیه به ازای هر توکن انجام میدهد.
- Beurer-Kellner et al., "Prompting Is Programming: A Query Language for Large Language Models" (LMQL) -- مقالهی LMQL که دیکود مقید را در قالب یک زبان پرسوجو با قیدهای نوعی و مقداری مطرح میکند.
- Microsoft Guidance (مستندات فریمورک) -- ابزاری برای تولید مقید مبتنی بر قالب؛ مکمل مستقل از سرویسدهنده برای Outlines و XGrammar.
