অধ্যায় 2 · চেইন ও স্ট্রাকচার্ড আউটপুট
JSON ও স্কিমা: ফিল্ড, টাইপ, enum ও অনুপস্থিত মান
- পৃষ্ঠা 7 / 24
- 16 মিনিট পড়া
মানুষ ফ্রি টেক্সট পড়ে। প্রোগ্রাম পড়ে না: বিলিং সিস্টেম, ক্যালেন্ডার বা চেইনের পরের ধাপের দরকার এমন ফিল্ড, যা সে খুঁজে পায় এবং যার টাইপে ভরসা করা যায়। মডেলকে দিয়ে সেই ফিল্ডগুলো নির্ভরযোগ্যভাবে ফেরত পাওয়া ব্যবহারিক প্রম্পটিংয়ের সবচেয়ে কাজের দক্ষতার একটা, আর ভুল করাও সবচেয়ে সহজ, কারণ যে উত্তর দেখতে JSON-এর মতো, সেটা এখনো আপনার প্রোগ্রামের ব্যবহারযোগ্য ডেটা নয়।
এই পাতাে দেখানো হবে JSON আউটপুট কীভাবে নিখুঁতভাবে নির্দিষ্ট করবেন, অনুপস্থিত মানের জন্য কীভাবে ডিজাইন করবেন, আর শুধু প্রম্পট দিয়ে কত দূর যাওয়া যায়। এটা চেইনের নির্ভরযোগ্যতা: এরর রেট, শুরুর ভুল, সংশোধন লুপ ও ফলব্যাক পাতাের পরের ধাপ। দরকার Python ৩.১২ বা নতুন, promptlab.py আর pip install jsonschema। ব্লকগুলো ক্রমে টাইপ করুন (এগুলো একই নাম ভাগ করে নেয়)। এখানকার "মডেল"গুলো টয় প্রোভাইডার (toy provider): সাধারণ বিচ্যুতি ইচ্ছা করে বসানো নির্ধারিত বদলি ফাংশন, যাতে যাচাইগুলো বারবার একইভাবে পরীক্ষা করা যায়। আসল মডেল কম অনুমানযোগ্য, তাই পরীক্ষা করতে হয়; টয়ের ফল আসল মডেল সম্পর্কে কিছু বলে না।
যা শিখবেন
- কখন কাঠামোবদ্ধ আউটপুট চাইবেন, আর JSON অবজেক্ট কীভাবে নির্দিষ্ট করবেন: সঠিক ফিল্ডের নাম, আবশ্যক ও ঐচ্ছিক ফিল্ড, টাইপ, enum, অনুপস্থিত মান, নেস্টেড অবজেক্ট ও অ্যারে।
- Markdown র্যাপার ছাড়া JSON কীভাবে চাইবেন, আর শুধু প্রম্পটের অনুরোধ কী নিশ্চিত করতে পারে না।
- প্রম্পট-করা JSON আর স্কিমা-সীমাবদ্ধ জেনারেশনের পার্থক্য, নিখুঁতভাবে।
ফ্রি-ফর্ম টেক্সট নাকি কাঠামোবদ্ধ আউটপুট
ফলাফল মানুষ পড়লে এবং শব্দচয়ন গুরুত্বপূর্ণ হলে ফ্রি-ফর্ম টেক্সট ব্যবহার করুন: সারাংশ, ইমেইলের খসড়া। কোড পড়লে কাঠামোবদ্ধ আউটপুট (structured output) ব্যবহার করুন: সংরক্ষণের রেকর্ড, শাখা ঠিক করার মান, পরের ধাপের ইনপুট। একই তথ্য দুভাবেই ফিরতে পারে। আমাদের চলমান উদাহরণ একটা মিটিং-রুমের অনুরোধ।
নমুনা উত্তর (উদাহরণ মাত্র; আপনারটা আলাদা হবে):
Aiko Tan would like the Sakura room on 3 November for about 45 minutes, with two
colleagues. She needs a projector. Her email is aiko@example.jp.মানুষের জন্য ঠিক আছে। প্রোগ্রামকে আন্দাজ করতে হবে রুম, তারিখ আর সময়কাল কোথায়। তুলনা করুন:
নমুনা উত্তর (উদাহরণ মাত্র; আপনারটা আলাদা হবে):
{"requester_name": "Aiko Tan", "requester_email": "aiko@example.jp", "room": "Sakura",
"date": "2026-11-03", "duration_minutes": 45, "needs_projector": true,
"attendees": [{"name": "Ben Ito", "email": null}, {"name": "Mei Sato", "email": null}]}এবার প্রতিটা তথ্যের একটা নাম ও একটা টাইপ আছে। এটা পেতে প্রম্পটকে হতে হবে স্পেসিফিকেশন, ইচ্ছাপ্রকাশ নয়।
অবজেক্ট নির্দিষ্ট করুন: নাম, আবশ্যক ফিল্ড, টাইপ ও enum
দুর্বল প্রম্পট:
Give me the booking details as JSON.এতে ফিল্ডের নাম, তারিখের ফরম্যাট, অনুপস্থিত ডেটার ব্যবস্থা আর আশপাশের লেখা ভাগ্যের ওপর ছেড়ে দেওয়া হয়। আজ room, কাল meeting_room আসতে পারে।
ভালো প্রম্পট:
Extract the meeting request below as ONE JSON object and nothing else.
No Markdown, no code fence, no text before or after the JSON.
Fields (use exactly these names, no others):
- requester_name: string
- requester_email: string, or null if not given
- room: one of "Sakura", "Maple", "Cedar", "unknown"
- date: "YYYY-MM-DD", or null if the request gives no exact date
- duration_minutes: integer, or null if not given
- needs_projector: true or false (false if not mentioned)
- attendees: array of {"name": string, "email": string or null}; [] if none
- notes: string, optional; leave the field out if there is nothing to add
Never guess a missing value: use null (or "unknown" for room).
### REQUEST
{request}| স্পেসিফিকেশনের উপাদান | ওপরের প্রম্পটে | কেন |
|---|---|---|
| ফিল্ডের সঠিক নাম | "use exactly these names, no others" | কোড নাম ধরে ফিল্ড খোঁজে; নাম বদলালে বা বাড়তি ফিল্ড এলে ভেঙে যায়। |
| আবশ্যক ও ঐচ্ছিক | notes ছাড়া সব ফিল্ড তালিকায়; notes বাদ দেওয়া যায় | আবশ্যক মানে "সবসময় আছে", দরকারে null; ঐচ্ছিক মানে "না-ও থাকতে পারে"। |
| টাইপ ও ফরম্যাট | string, integer, boolean, array, YYYY-MM-DD | সংখ্যা সংখ্যা হিসেবে, তারিখ এক ফরম্যাটে। |
| enum (গণনা) | room চারটা মানের একটা | বদ্ধ তালিকা তুলনা করা যায়, মুক্ত লেবেল যায় না। "unknown" বা "other"-এর মতো স্পষ্ট বিকল্প রাখুন। |
| অনুপস্থিত মান | "use null", কখনো আন্দাজ নয় | নিচে দেখুন। |
| নেস্টেড ডেটা | attendees: ছোট অবজেক্টের অ্যারে | এক স্তর নেস্টিং সাধারণত যথেষ্ট; গভীর নেস্টিংয়ে ভুল বাড়ে। |
আবশ্যক ও ঐচ্ছিক ফিল্ড
আবশ্যক ফিল্ড সবসময় থাকতে হবে, মান অজানা হলেও (তখন null)। ঐচ্ছিক ফিল্ড না-ও থাকতে পারে। ঐচ্ছিকের তালিকা ছোট রাখুন: প্রতিটা ঐচ্ছিক ফিল্ড এমন একটা জায়গা যেখানে "নেই" মানেটা অস্পষ্ট। এখানে শুধু notes ঐচ্ছিক, কারণ "যোগ করার কিছু নেই" একটা আলাদা, বাস্তব অবস্থা।
টাইপ, অনুমোদিত মান ও enum
প্রতিটা ফিল্ডের টাইপ বলুন, আর ফরম্যাট আছে এমন স্ট্রিংয়ের ফরম্যাটও (তারিখ YYYY-MM-DD, "3 Nov" নয়)। যেকোনো নির্দিষ্ট শ্রেণির জন্য (স্ট্যাটাস, রুম, মুদ্রার কোড) enum ব্যবহার করুন: মডেলকে আপনার তালিকা থেকে বাছতে হয়, আর কোড আন্দাজ-মেলানো ছাড়াই মান তুলনা করতে পারে। প্রতিটা মান ঠিক যেভাবে আসা চাই সেভাবে লিখুন, বড় হাতের অক্ষর সহ, আর "এগুলোর কোনোটাই নয়" বোঝাতে একটা মান রাখুন, যাতে মডেল ভুল বিকল্প বাছতে বাধ্য না হয়।
নেস্টেড অবজেক্ট ও অ্যারে
একটা অনুরোধে একাধিক মানুষ বা আইটেম থাকলে অবজেক্টের অ্যারে ব্যবহার করুন এবং প্রতিটা আইটেমের কি-গুলোর নাম দিন (ওপরে attendees)। খালি তালিকা কেমন দেখাবে বলুন ([], null নয়), আর আইটেমের কোন ফিল্ড null হতে পারে তাও। নেস্টিং এক বা দুই স্তরে রাখুন; প্রতি অতিরিক্ত স্তরে কোনো কি (key) বাদ পড়া বা টাইপ ভুল হওয়ার সুযোগ বাড়ে।
একই স্পেসিফিকেশন দ্বিতীয়বার লিখুন যন্ত্র-পাঠযোগ্য JSON Schema হিসেবে, আর কোডকে দিয়ে উত্তর মিলিয়ে নিন। নিচের স্কিমা প্রম্পটের চুক্তিটাই কোডে: টাইপ, একটা enum, তারিখের প্যাটার্ন, একটা সর্বনিম্ন মান, নেস্টেড অবজেক্টের অ্যারে, ঐচ্ছিক notes, আর additionalProperties: False, যাতে বানানো ফিল্ড বাতিল হয়। problems হেল্পার ছোট বার্তা ফেরত দেয়; দ্বিতীয় কলে ইচ্ছা করে চারটা ভুল রাখা হয়েছে।
import json, re
from jsonschema import Draft202012Validator
ROOMS = ["Sakura", "Maple", "Cedar"]
SCHEMA = {
"type": "object",
"properties": {
"requester_name": {"type": "string", "minLength": 1},
"requester_email": {"type": ["string", "null"]},
"room": {"enum": ROOMS + ["unknown"]}, # fixed categories, with an explicit escape
"date": {"type": ["string", "null"], "pattern": r"^\d{4}-\d{2}-\d{2}$"}, # null when the request gives no date
"duration_minutes": {"type": ["integer", "null"], "minimum": 1},
"needs_projector": {"type": "boolean"},
"attendees": {"type": "array", "items": { # array of nested objects
"type": "object",
"properties": {"name": {"type": "string"}, "email": {"type": ["string", "null"]}},
"required": ["name", "email"], "additionalProperties": False}},
"notes": {"type": "string"}, # optional: not in "required"
},
"required": ["requester_name", "requester_email", "room", "date", "duration_minutes", "needs_projector", "attendees"],
"additionalProperties": False, # no invented fields
}
Draft202012Validator.check_schema(SCHEMA) # the schema itself is valid
def problems(doc):
"""Short, sorted schema problems (sorted so the output is stable)."""
out = []
for e in Draft202012Validator(SCHEMA).iter_errors(doc):
msg = e.message if e.validator in ("required", "additionalProperties") else f"{e.instance!r} fails {e.validator}"
out.append(f"{'/'.join(map(str, e.path)) or '(top)'}: {msg}")
return sorted(out)
good = {"requester_name": "Aiko Tan", "requester_email": "aiko@example.jp", "room": "Sakura", "date": "2026-11-03",
"duration_minutes": 45, "needs_projector": True, "attendees": [{"name": "Ben Ito", "email": None}]}
print(problems(good))
print(problems({**good, "room": "sakura", "duration_minutes": "45", "colour": "red", "attendees": [{"name": "Ben Ito"}]}))[]
["(top): Additional properties are not allowed ('colour' was unexpected)", "attendees/0: 'email' is a required property", "duration_minutes: '45' fails type", "room: 'sakura' fails enum"]অনুপস্থিত মান স্পষ্টভাবে দেখান
অনুরোধে তারিখ না থাকলে ফিল্ড ভরতে বাধ্য মডেল প্রায়ই একটা বানিয়ে ফেলে। কী বসাতে হবে বলে দিন, আর স্কিমাকে ঠিক সেটাই মানতে দিন। "অজানা" বোঝানোর চারটা উপায় তুলনা করুন:
for label, patch in [("date null", {"date": None}), ("date empty string", {"date": ""}), ("date 'N/A'", {"date": "N/A"}),
("duration null", {"duration_minutes": None}), ("duration 0", {"duration_minutes": 0}),
("room unknown", {"room": "unknown"}), ("email missing key", {"requester_email": ...})]:
doc = {k: v for k, v in {**good, **patch}.items() if v is not ...}
print(f"{label:18}", problems(doc) or "valid")date null valid
date empty string ["date: '' fails pattern"]
date 'N/A' ["date: 'N/A' fails pattern"]
duration null valid
duration 0 ['duration_minutes: 0 fails minimum']
room unknown valid
email missing key ["(top): 'requester_email' is a required property"]শুধু null (আর "unknown" রুম) পাস করে। খালি স্ট্রিং, "N/A" বা 0 মানের মতো দেখায় এবং কারও নজরে না এসে পরের ধাপে ঢুকে যেত, তাই প্যাটার্ন ও সর্বনিম্ন মান সেগুলো বাতিল করে। ফিল্ডকে ঐচ্ছিক না করে আবশ্যক কিন্তু null-যোগ্য করা ভালো: কোনো কি (key) বাদ পড়লে বোঝা যায় না তথ্যটা অনুরোধে ছিল না, নাকি মডেল ভুলে গেছে, কিন্তু null একটা স্পষ্ট উত্তর। প্রতিটা ফিল্ডের জন্য প্রশ্নটা হলো: "অজানা" পেলে প্রোগ্রাম কী করবে?
Markdown র্যাপার ছাড়া বৈধ JSON
শুধু JSON চান: "one JSON object and nothing else, no Markdown, no code fence, no text before or after"। প্রোগ্রাম পুরো উত্তর পার্স করে, তাই একটা ভদ্র বাক্য বা ```json ফেন্স পার্সিং ভেঙে দেয়। নির্দেশটা লিখুন উত্তর কী হবে তার ইতিবাচক বর্ণনা হিসেবে, তারপরও যাচাই করুন, কারণ প্রম্পট একটা অনুরোধ।
শুধু প্রম্পটে JSON চাওয়ার সীমা
পরের ব্লক ১২টা বানানো অনুরোধে একটা টয় প্রোভাইডার চালায়। এতে সাধারণ বিচ্যুতিগুলো ইচ্ছা করে রাখা হয়েছে: কোড ফেন্স, JSON-এর আগে লেখা, শেষের কমা, স্ট্রিং হয়ে যাওয়া সংখ্যা, বাড়তি শব্দসহ enum মান, বাড়তি ফিল্ড, বাদ পড়া ফিল্ড আর মাঝপথে কেটে যাওয়া উত্তর। বিচ্যুতির মিশ্রণ বানানো; আসল হার মডেল ও প্রম্পটের ওপর নির্ভর করে, তাই নিজের মাপুন।
from promptlab import Client, render
PEOPLE = [("Aiko Tan", "aiko@example.jp", "Sakura", "2026-11-03", 45, ["Ben Ito", "Mei Sato"], True),
("Kwame Mensah", "kwame@example.gh", "Maple", "2026-11-04", 60, [], False),
("Lena Fischer", "lena@example.de", "Cedar", None, 30, ["Omar Haddad"], False),
("Priya Nair", "priya@example.in", "Sakura", "2026-11-05", 120, ["Nadia Rahman"], True)] * 3
MODES = ["clean", "fence", "clean", "prose", "comma", "clean", "string_number", "bad_enum", "clean", "extra_key", "missing_key", "truncated"]
def request_text(k, p):
name, mail, room, date, mins, others, proj = p
when = f"on {date}" if date else "sometime next week"
dur = f"{mins // 60} hours" if mins % 60 == 0 and mins >= 120 else f"{mins} minutes"
who = f" {' and '.join(others)} will join." if others else ""
return f"#{k}: Hi, this is {name} ({mail}). Please book {room} {when} for {dur}.{who}" + (" We need a projector." if proj else "")
REQUESTS = [request_text(k, p) for k, p in enumerate(PEOPLE)]
GOLD = [{"requester_name": n, "requester_email": m, "room": r, "date": d, "duration_minutes": t,
"needs_projector": pr, "attendees": [{"name": a, "email": None} for a in o]} for n, m, r, d, t, o, pr in PEOPLE]
print(REQUESTS[0]); print(REQUESTS[3])
def toy_prompted(messages, **settings):
"""Stand-in for a model that was only ASKED for JSON. It answers correctly, then applies one typical
drift per request. The mix of drifts is invented for teaching; real rates differ by model and prompt."""
k = int(re.search(r"#(\d+):", messages[-1]["content"]).group(1))
rec, mode = json.loads(json.dumps(GOLD[k])), MODES[k]
if mode == "string_number": rec["duration_minutes"] = str(rec["duration_minutes"])
if mode == "bad_enum": rec["room"] += " Room"
if mode == "extra_key": rec["confidence"] = 0.9
if mode == "missing_key": del rec["requester_email"]
text = json.dumps(rec)
return {"fence": f"```json\n{text}\n```", "prose": f"Here is the JSON you asked for:\n{text}",
"comma": text[:-1] + ",}", "truncated": text[:70]}.get(mode, text)#0: Hi, this is Aiko Tan (aiko@example.jp). Please book Sakura on 2026-11-03 for 45 minutes. Ben Ito and Mei Sato will join. We need a projector.
#3: Hi, this is Priya Nair (priya@example.in). Please book Sakura on 2026-11-05 for 2 hours. Nadia Rahman will join. We need a projector.PROMPT = "Extract the meeting request as JSON.\n### REQUEST\n{request}" # the toy ignores the wording; your checks matter here
def classify(text):
try: doc = json.loads(text)
except json.JSONDecodeError as exc: return f"not JSON ({exc.msg})"
return "valid" if not problems(doc) else "schema: " + problems(doc)[0][:48]
client = Client(toy_prompted)
raw = [client.complete([{"role": "user", "content": render(PROMPT, request=r)}]).text for r in REQUESTS]
for k, text in enumerate(raw): print(f"#{k:<2}", classify(text))
print("valid:", sum(classify(t) == "valid" for t in raw), "of", len(raw))#0 valid
#1 not JSON (Expecting value)
#2 valid
#3 not JSON (Expecting value)
#4 not JSON (Expecting property name enclosed in double quotes)
#5 valid
#6 schema: duration_minutes: '30' fails type
#7 schema: room: 'Sakura Room' fails enum
#8 valid
#9 schema: (top): Additional properties are not allowed ('c
#10 schema: (top): 'requester_email' is a required property
#11 not JSON (Expecting ',' delimiter)
valid: 4 of 12১২টার মধ্যে ব্যবহারযোগ্য মাত্র ৪টা, আর প্রতিটা ব্যর্থতা আলাদা। সিনট্যাক্স পার্সার ধরে চারটা; স্কিমা আরও চারটা ধরে, যেগুলো বৈধ JSON (ভুল টাইপ, ভুল enum মান, বাড়তি কি, বাদ পড়া কি)। তাই দুটোই যাচাই করতে হয়। প্রচলিত একটা জোড়াতালি হলো প্রথম { থেকে শেষ } পর্যন্ত অংশ কেটে নেওয়া, যা র্যাপার সরায় কিন্তু বিষয়বস্তু সারাতে পারে না:
from collections import Counter
def extract_json(text):
"""Defensive rescue: keep the text from the first '{' to the last '}'. It removes wrappers; it cannot repair content."""
start, end = text.find("{"), text.rfind("}")
return text[start:end + 1] if 0 <= start < end else text
after = [classify(extract_json(t)) for t in raw]
print("valid after rescue:", after.count("valid"), "of", len(raw))
print(sorted(Counter(a.split(":")[0].split(" (")[0] for a in after if a != "valid").items()))
def toy_constrained(messages, **settings):
"""Stand-in for schema-constrained generation: every reply parses and fits SCHEMA, but a value can still be wrong
(here it reads the first number after 'for', so '2 hours' becomes 2 minutes)."""
k = int(re.search(r"#(\d+):", messages[-1]["content"]).group(1))
rec = json.loads(json.dumps(GOLD[k]))
rec["duration_minutes"] = int(re.search(r"for (\d+)", messages[-1]["content"]).group(1))
return json.dumps(rec)
cons = Client(toy_constrained)
out = [json.loads(cons.complete([{"role": "user", "content": render(PROMPT, request=r)}]).text) for r in REQUESTS]
print("fit the schema:", sum(not problems(d) for d in out), "of", len(out))
print("equal to the true record:", sum(d == g for d, g in zip(out, GOLD)), "of", len(out))
print("wrong duration in requests:", [k for k, (d, g) in enumerate(zip(out, GOLD)) if d != g])valid after rescue: 6 of 12
[('not JSON', 2), ('schema', 4)]
fit the schema: 12 of 12
equal to the true record: 9 of 12
wrong duration in requests: [3, 7, 11]এই উদ্ধার শুধু ফেন্স আর ভূমিকা-লেখার কেস ঠিক করে, আর কিছু নয়: ২টা উত্তর এখনো JSON নয়, ৪টা এখনো স্কিমায় ব্যর্থ। শেষ তিন লাইন পাঠের বাকি অর্ধেক দেখায়: স্কিমা-সীমাবদ্ধ আউটপুটে প্রতিটা উত্তর স্কিমায় মেলে, তবু ৩টা মান ভুল (বদলি ফাংশন "2 hours"-কে ২ পড়ে)। ফরম্যাট আর সত্য আলাদা সমস্যা।
প্রম্পট-করা JSON ও স্কিমা-সীমাবদ্ধ জেনারেশন
প্রম্পট-করা JSON প্রম্পটে একটা অনুরোধ; মডেল সাধারণত মানে, মাঝে মাঝে মানে না, যেমন ওপরে। স্কিমা-সীমাবদ্ধ জেনারেশন (schema-constrained generation) কিছু প্রোভাইডারের ফিচার: আপনি স্কিমা দেন, আর ডিকোডার সেই স্কিমায় মেলে এমন আউটপুটেই সীমাবদ্ধ থাকে। পার্থক্যটা নিখুঁতভাবে বলা যাক:
| প্রম্পটে JSON চাওয়া | স্কিমা-সীমাবদ্ধ আউটপুট | |
|---|---|---|
| পার্সযোগ্য, সঠিক ফিল্ড ও টাইপ | সাধারণত, নিশ্চিত নয় | জেনারেশন স্বাভাবিকভাবে শেষ হলে নিশ্চিত |
| মান সঠিক | না | না: মান ভুল বা বানানো হতে পারে |
| সবসময় শেষ হয় | না | না: প্রত্যাখ্যান (refusal) বা টোকেন-সীমায় কেটে যাওয়ায় স্কিমায় না-মেলা আউটপুট আসতে পারে; পার্স করার আগে স্টপ স্ট্যাটাস দেখুন |
| JSON Schema-র সব কিওয়ার্ড | যা লিখুন (কিন্তু প্রয়োগ হয় না) | শুধু একটা উপসেট; অসমর্থিত কিওয়ার্ড বাতিল হতে পারে, তাই শর্তগুলো Python-এ আবার যাচাই করুন |
প্রোভাইডার নোট (২০২৬-১০-১২ অনুযায়ী): OpenAI-এর ডকুমেন্টেশন বলে Structured Outputs স্কিমা মেনে চলা নিশ্চিত করে, আর JSON mode শুধু বৈধ JSON নিশ্চিত করে; সব ফিল্ড required হতে হয় (ঐচ্ছিক ফিল্ড লিখতে হয় null-অনুমোদনকারী টাইপ দিয়ে),
additionalPropertiesfalse হতে হয়, আরallOfওif/then/else-এর মতো কিছু কিওয়ার্ড অসমর্থিত; আর প্রত্যাখ্যান আসে আলাদা ফিল্ডে এবং টোকেন-সীমায় কেটে গেলে অসম্পূর্ণ (incomplete) রেসপন্স আসে। Anthropic-এর ডকুমেন্টেশনে constrained decoding দিয়ে স্কিমা-সম্মত রেসপন্সের কথা আছে, সংখ্যা বা স্ট্রিং-দৈর্ঘ্যের শর্ত সমর্থিত নয়, আর স্টপ কারণrefusalবাmax_tokensহলে স্কিমায় না-মেলা আউটপুট আসতে পারে। Google-এর ডকুমেন্টেশনে JSON Schema-র একটা সমর্থিত উপসেটের কথা আছে, এবং বলা আছে মানগুলো নিজের অ্যাপ্লিকেশনে যাচাই করতে। OpenAI ও Google সতর্ক করে যে স্কিমায় মেলা আউটপুটেও ভুল মান থাকতে পারে। সীমা আলাদা এবং বদলায়: বর্তমান ডকুমেন্টেশন দেখুন।
তাই সব প্রোভাইডারে যে নিয়ম টেকে: JSON যেভাবেই পান, নিজের কোডে তিনটা জিনিস যাচাই করুন: সিনট্যাক্স, স্কিমা আর বিজনেস রুল (তারিখ ভবিষ্যতের কি না, যোগফল মেলে কি না)। পরের পাতা ভ্যালিডেশন ও রিপেয়ার: jsonschema, বিজনেস রুল ও সীমিত রিট্রাই ঠিক সেই যাচাইগুলোই বানায়।
কাজের জায়গায় এটা কোথায় দেখা যায়
ইনভয়েস এক্সট্র্যাকশন, টিকেট রাউটিং, CRM আপডেট, ফর্ম পূরণ এবং কোডকে ইনপুট জোগায় এমন যেকোনো চেইন-ধাপ কাঠামোবদ্ধ আউটপুট ব্যবহার করে। দলগুলো সাধারণত প্রম্পট-করা JSON দিয়ে শুরু করে, প্রোভাইডার সমর্থন করলে স্কিমা-সীমাবদ্ধ আউটপুট যোগ করে, আর দুই ক্ষেত্রেই নিজেদের যাচাই রাখে।
সাধারণ ভুল
- "JSON-এর মতো দেখায়" মানে বৈধ ধরে নেওয়া। সবসময় পার্সার আর স্কিমা-চেক চালান।
- অনুপস্থিত মান মডেলকে ভরতে দেওয়া।
nullবা স্পষ্ট অজানা দিন, আর দেখতে-একই-রকম মান বাতিল করুন। - enum-এর বদলে মুক্ত লেবেল। এক শ্রেণির দশ বানান পাবেন। বিকল্প-মানসহ বদ্ধ তালিকা ব্যবহার করুন।
- সীমাবদ্ধ আউটপুটকে সঠিক আউটপুট ভাবা। এটা শুধু ফরম্যাট নিশ্চিত করে, আর প্রত্যাখ্যান বা কেটে যাওয়ার পরে সেটুকুও নয়।
- গভীর, জটিল স্কিমা। ছোট ও সমতল রাখুন: ভুলের জায়গা কম, সমর্থিত হওয়ার সম্ভাবনা বেশি।
নিজে চেষ্টা করুন
ওপরের একই সেশনে।
- সহজ। অফিসে চতুর্থ রুম "Online" যোগ হয়েছে। স্কিমাকে সেটা মানতে দিন এবং দেখান ছোট হাতের "online" এখনো বাতিল হয়। প্রম্পটের কোন লাইনও বদলাতে হবে?
- মাঝারি। প্রম্পটে বলা আছে না বললে
needs_projectorহবেfalse। ব্যাখ্যা করুন কেন এটা একটা আন্দাজ লুকায়, তারপর স্কিমা বদলান যাতেnullমানে "অনুরোধে বলা হয়নি", এবংTrue,Noneও"yes"পরীক্ষা করুন। - কঠিন।
parse_strict(text)লিখুন: র্যাপারহীন JSON, অথবা ঠিক একটা ফেন্সড ব্লক (আর কিছু নেই) মানবে; বাকি সব কারণসহ বাতিল করবে। ওপরের ০, ১, ৩ ও ৪ নম্বর উত্তরে চালান এবং বলুন এই নীতির দাম কী।
উত্তর
সহজ।
SCHEMA["properties"]["room"]["enum"].append("Online")
print(problems({**good, "room": "Online"}) or "valid", problems({**good, "room": "online"}))valid ["room: 'online' fails enum"]পাস চেকলিস্ট: নতুন মানটা স্কিমায় যোগ হয়েছে; ছোট হাতের মান বাতিল হয়েছে; প্রম্পটের room লাইনে চতুর্থ মান যোগ হয়েছে।
মাঝারি। প্রম্পট মডেলকে জানা না থাকলেও "না" বলতে বাধ্য করে, তাই অজানা আর "না" একই রকম দেখায়। পাস চেকলিস্ট: null অনুমোদিত এবং প্রম্পটে "বলা হয়নি" হিসেবে লেখা; "yes"-এর মতো স্ট্রিং বাতিল; null-এর মানে কী তা কোড ঠিক করে (ব্যক্তিকে জিজ্ঞেস করা, অথবা "না" ধরা)।
SCHEMA["properties"]["needs_projector"] = {"type": ["boolean", "null"]} # null = the request did not say
for v in (True, None, "yes"):
print(repr(v), problems({**good, "needs_projector": v}) or "valid")True valid
None valid
'yes' ["needs_projector: 'yes' fails type"]কঠিন।
def parse_strict(text):
"""Accept bare JSON, or ONE fenced block with nothing else around it. Return (document, note)."""
body = text.strip()
fenced = re.fullmatch(r"```(?:json)?\n(.*)\n```", body, re.S)
try: return json.loads(fenced.group(1) if fenced else body), "fenced" if fenced else "clean"
except json.JSONDecodeError as exc: return None, f"rejected: {exc.msg}"
for k in (0, 1, 3, 4):
print(k, *parse_strict(raw[k])[1:])0 clean
1 fenced
3 rejected: Expecting value
4 rejected: Expecting property name enclosed in double quotesপাস চেকলিস্ট: র্যাপারহীন JSON আর একক ফেন্স গৃহীত হয়েছে (ফেন্স চিহ্নিত); JSON-এর আগে লেখা আর শেষের কমা পার্সারের কারণসহ বাতিল; কিছুই আন্দাজ করা হয়নি। দাম: যে উত্তর উদ্ধার-ফাংশন বাঁচাতে পারত তা বাতিল হয়, তাই রিট্রাই করতে হয় বা থামতে হয়। এটা ইচ্ছাকৃত পছন্দ, আর পরের পাতা সীমিত রিট্রাই দেখাবে।
সারসংক্ষেপ
- মানুষের জন্য ফ্রি-ফর্ম টেক্সট, কোডের জন্য কাঠামোবদ্ধ আউটপুট; প্রম্পটকে স্পেসিফিকেশন হিসেবে লিখুন: সঠিক নাম, আবশ্যক ও ঐচ্ছিক ফিল্ড, টাইপ, বিকল্পমানসহ enum, স্পষ্ট null।
- ঐচ্ছিকের চেয়ে আবশ্যক-কিন্তু-null-যোগ্য ফিল্ড ভালো; প্যাটার্ন ও সর্বনিম্ন মান দিয়ে নকল অনুপস্থিত মান বাতিল করুন।
- Markdown র্যাপার ছাড়া শুধু JSON চান, আর তবু পার্স ও যাচাই করুন: প্রম্পট অনুরোধ, নিশ্চয়তা নয়।
- স্কিমা-সীমাবদ্ধ জেনারেশন ফরম্যাট নিশ্চিত করে, সত্য নয়; প্রত্যাখ্যান বা কেটে যাওয়ায় ব্যর্থ হতে পারে; আর JSON Schema-র শুধু একটা উপসেট সমর্থন করে।
- সিনট্যাক্স, স্কিমা ও বিজনেস রুল সবসময় নিজের কোডে যাচাই করুন।
এরপর: ভ্যালিডেশন ও রিপেয়ার: jsonschema, বিজনেস রুল ও সীমিত রিট্রাই, যেখানে সেই যাচাইগুলো এবং একটা নিরাপদ, সীমিত রিপেয়ার লুপ বানাবেন।