এই পর্বে যা শিখবেন
- কেন structured output, আর একটি JSON Schema লাইন ধরে পড়া
- JSON mode, schema-বাঁধা output আর function calling-এর পার্থক্য, ছয়টি API-র নথি থেকে
- jsonschema আর Pydantic দিয়ে যাচাই, আর repair-retry loop
- বাংলা অঙ্ক, দাম আর জেলার নাম এক রূপে আনা
- Constrained decoding: token mask আর ব্যাকরণ, আর একটি ছোট model-এ চার পদ্ধতির মাপা তুলনা
এই পর্বের desktop run: Python 3.12, torch 2.15.0 (CPU), transformers 5.22.2, jsonschema 4.26.0, pydantic 2.14.3, lm-format-enforcer 0.11.3, লেখার সময়ে। Model Qwen/Qwen2.5-0.5B-Instruct (Apache 2.0, token ছাড়া নামানো যায়)। ৩০টি অর্ডার-লেখা এই পর্বের জন্য হাতে লেখা কাল্পনিক বাজারঘর ডেটা; কোনো আসল মানুষ, ফোন নম্বর বা ঠিকানা নেই। সব সংখ্যা একটি স্থানীয় run-এর, একটি ছোট model-এর। বড় hosted পণ্য অন্যভাবে আচরণ করতে পারে, আর এই পাতার কোনো সংখ্যা দিয়ে কোনো পণ্যের মান বিচার করা যায় না। JSON ফাইলের মূল কথা AI-এর ভিত্তি কোর্সের পর্ব ১০-এ আর Python প্রোগ্রামিং কোর্সের পর্ব ২৭-এ; LLM-এর মূল ধারণা AI-এর ভিত্তি কোর্সের পর্ব ৭০-এ।
১ · কেন structured output — মানুষের লেখা থেকে যন্ত্রের ফর্ম
ব্যাংকের জমা-স্লিপের কথা ভাবুন। গ্রাহক মুখে যা-ই বলুন, ক্যাশিয়ার স্লিপে নির্দিষ্ট ঘর ভরেন: হিসাব নম্বর, টাকার অঙ্ক, অঙ্কে আর কথায়। ঘর ফাঁকা বা অঙ্ক না মিললে স্লিপ ফেরত যায়। LLM-এর structured output ঠিক এই স্লিপ: model মুক্ত ভাষা পড়ে, কিন্তু উত্তর দেয় নির্দিষ্ট ঘরে, আর আপনার প্রোগ্রাম ক্যাশিয়ারের মতো প্রতিটি ঘর মিলিয়ে নেয়।
বাজারঘরে প্রতিদিন Messenger আর ফোনে শত শত অর্ডার আসে: “রাজশাহীতে ২টা সুতির শাড়ি পাঠাবেন, মোট ১,৫৫০ টাকা, ক্যাশ অন ডেলিভারি।” গুদামের software-কে জানতে হবে জেলা, পণ্য, সংখ্যা, দাম আর টাকা কখন আসবে। Model যদি লেখে “জি, অর্ডারটি রাজশাহীর জন্য, দুইটি শাড়ি…”, মানুষ বোঝে, প্রোগ্রাম বোঝে না। প্রোগ্রাম চায় এমন কিছু:
{"district": "রাজশাহী", "product": "সুতির শাড়ি", "quantity": 2,
"price_taka": 1550, "cod": true}
এটি JSON: চাবি আর মানের জোড়া, লেখা উদ্ধৃতিচিহ্নে, সংখ্যা উদ্ধৃতি ছাড়া, true/false ছোট হাতের। LLM-কে এই আকারে উত্তর দেওয়ানোকেই বলে structured output. ব্যবহার অনেক: অর্ডার বা রসিদ থেকে তথ্য তোলা, রিভিউ শ্রেণিতে ভাগ, গ্রাহকের বার্তা থেকে টিকিট বানানো, আর পর্ব ৭১ · Tool use আর function calling-এ দেখবেন, model-কে দিয়ে একটি function ডাকানো।
সমস্যা হলো, LLM শেষ পর্যন্ত token-এর পর token বাছাই করে (পর্ব ৫০)। “JSON দাও” বললে সে JSON-এর মতো দেখতে লেখা দেয়, কিন্তু নিশ্চয়তা নেই। সামনে কথা জুড়ে দিতে পারে, ```json বেড়া দিতে পারে, কমা ভুলতে পারে, “Rajshahi” লিখতে পারে, বা দাম লিখতে পারে "১,৫৫০" লেখা হিসেবে। নিচের পথটি প্রতিটি ধাপে এসব ধরার জন্য।
২ · JSON Schema — উত্তরের নিয়ম লিখে রাখা
JSON Schema হলো JSON দিয়েই লেখা একটি নিয়মপত্র: কোন চাবি থাকবে, প্রতিটির ধরন কী, কোন মান চলবে। এটি একটি খোলা মান (json-schema.org; এখনকার draft 2020-12). বাজারঘরের অর্ডারের schema:
{
"type": "object",
"properties": {
"district": {"type": "string", "enum": ["ঢাকা", "চট্টগ্রাম", "রাজশাহী",
"খুলনা", "সিলেট", "বরিশাল", "রংপুর", "ময়মনসিংহ",
"কুমিল্লা", "গাজীপুর"]},
"product": {"type": "string", "minLength": 1},
"quantity": {"type": "integer", "minimum": 1, "maximum": 50},
"price_taka": {"type": "integer", "minimum": 1, "maximum": 200000},
"cod": {"type": "boolean"}
},
"required": ["district", "product", "quantity", "price_taka", "cod"],
"additionalProperties": false
}
প্রতিটি শব্দের অর্থ: type ধরন বলে (string, integer, number, boolean, object, array, null). properties প্রতিটি চাবির নিজের নিয়ম। required কোন চাবি বাদ দেওয়া যাবে না। additionalProperties: false মানে তালিকার বাইরে কোনো চাবি নয়: model নিজে থেকে "phone" যোগ করলে বাতিল। enum মানে নির্দিষ্ট তালিকার একটি মানই চলবে। minimum/maximum সংখ্যার সীমা, minLength লেখার ন্যূনতম দৈর্ঘ্য।
এই schema-তে ইচ্ছে করে ফোন নম্বরের ঘর নেই। যে তথ্য কাজে লাগে না, তা তোলাই উচিত নয়; additionalProperties: false সেটি জোর করে। নিচের stepper-এ একটি সত্যিকারের model output এই schema-র সঙ্গে ধাপে ধাপে মেলানো হয়েছে।
| ধাপ | |
| schema-র নিয়ম | |
| model-এর মান | |
| schema-র রায় | |
| আসল উত্তর |
- type. প্রথম নিয়ম, পুরো উত্তর একটি object কি না। Qwen2.5-0.5B-এর এই উত্তর (retry পদ্ধতির প্রথম চেষ্টা,
```jsonবেড়া সরানোর পরে) একটি object, তাই পার। - required আর additionalProperties. পাঁচটি চাবিই আছে, বাড়তি কিছু নেই। আকারের দিক থেকে উত্তরটি সম্পূর্ণ।
- district-এর enum. মান “লেট”, “সিলেট”-এর শেষ অংশ। দশটি অনুমোদিত নামের কোনোটি নয়, তাই এখানে schema ভুল ধরে।
- product. “সিলেট” একটি অ-খালি string, তাই schema-র চোখে ঠিক। অথচ পণ্য ছাতা; জেলার নাম ভুল ঘরে চলে এসেছে।
- quantity আর price_taka. 2 ঠিক। 8800 সীমার ভেতরে, তাই schema পার; কিন্তু অর্ডারে মোট ৮৮০ টাকা, একটি শূন্য বেশি।
- cod আর রায়. true ঠিক। Schema ধরল মাত্র ১টি ভুল (জেলা); অর্থের দিক থেকে ভুল ৩টি। Schema আকার দেখে, সত্য নয়; বাকি দুটি ধরতে লাগে ব্যবসার নিয়ম (দাম মূল লেখার সংখ্যার সঙ্গে মেলে কি?) আর eval set.
Schema পার মানেই সঠিক নয়। “ঢাকা” enum-এ আছে; অর্ডার রাজশাহীর হলেও “ঢাকা” schema পার হবে। Schema আকার পরীক্ষা করে, সত্য নয়। অর্থের পরীক্ষা আলাদা: field accuracy, যা §৭-এ মাপা হয়েছে।
৩ · তিন স্তরের উপায় — prompt, JSON mode আর schema-বাঁধা output
সব API-তে মূল ধারণা একই, তিন স্তরে:
১. শুধু prompt
নির্দেশে লিখে দেওয়া: “শুধু JSON দাও, এই চাবিগুলো।” কোনো নিশ্চয়তা নেই; ছোট model প্রায়ই ভাঙে। যেকোনো model-এ চলে।
২. JSON mode
Server নিশ্চিত করে উত্তর বৈধ JSON, কিন্তু কোন চাবি বা ধরন, তা নয়। Schema-র কাজ তখনো prompt আর আপনার যাচাইয়ের।
৩. Schema-বাঁধা output বা strict tool
আপনি schema পাঠান; server decoding-এর সময় তা মানায়। কিছু keyword সমর্থিত নয়, আর মান ভুল হতেই পারে।
Function calling (tool calling) একই কৌশলের আরেক রূপ। আপনি function-এর নাম, বর্ণনা আর parameter-এর JSON Schema দেন; model উত্তর না লিখে বলে “এই function, এই argument দিয়ে”। Argument-টি JSON, তাই এখানেও একই যাচাই লাগে। Function চালানো আর ফল ফেরত দেওয়ার পুরো চক্র পর্ব ৭১ · Tool use আর function calling-এ।
নিচের সারণি প্রতিটি কোম্পানির নিজের API নথি থেকে, লেখার সময়ে (নভেম্বর ২০২৬) পড়া; নাম আর নিয়ম পরিবর্তন হতে পারে, ব্যবহারের আগে নথিটি নিজে খুলে মিলিয়ে নিন। যেখানে নথিতে বিষয়টি খুঁজে পাইনি, সেখানে তা-ই লেখা। কোনটি “ভালো”, এই সারণি তা বলে না।
| API (কোম্পানি) | JSON mode | Schema-বাঁধা output | Tool-এ কড়া schema | লক্ষ করার মতো |
|---|---|---|---|---|
| OpenAI (ChatGPT-এর API) | json_object; context-এ “JSON” শব্দ না থাকলে error | Responses-এ text.format, Chat Completions-এ response_format: json_schema + strict | Function-এ strict: true, নথি সবসময় চালু রাখতে বলে | সব চাবি required, ঐচ্ছিক হলে null-সহ ধরন; allOf, if/then নেই; নতুন schema-র প্রথম অনুরোধে বাড়তি সময় |
| Google (Gemini API) | responseMimeType: "application/json" | সঙ্গে responseJsonSchema; নতুন Interactions API-তে response_format-এর ভেতরে schema | Function calling-এর validated mode schema মানানোর কথা বলে | minLength, maxLength-এর মতো কিছু keyword decoding-এ মানা হয় না, নথি নিজে যাচাই করতে বলে; enum-এর জন্য text/x.enum |
| Anthropic (Claude API) | আলাদা নামে নথিতে পাইনি | output_config.format: json_schema; নথি অনুযায়ী schema থেকে grammar বানিয়ে constrained decoding | Tool-এ strict: true | minimum, maximum, minLength সমর্থিত নয়; refusal বা max_tokens-এ schema না-ও মিলতে পারে; grammar ২৪ ঘণ্টা cache |
| xAI (Grok API) | json_object | response_format: json_schema; সমর্থিত keyword-এ মিলের নিশ্চয়তা | নথি অনুযায়ী tool-এ strict সবসময় চালু | additionalProperties নিজে থেকেই false; সমর্থনের বাইরের keyword “best-effort” |
| DeepSeek | json_object; prompt-এ “json” শব্দ আর উদাহরণ দিতে বলে | নথিতে পাইনি | Beta: tool-এ strict: true, beta base URL | উত্তর শুধু ফাঁকা জায়গা আসতে পারে, নথি retry রাখতে বলে; strict-এ minLength নেই, minimum আছে |
| Moonshot AI (Kimi API) | json_object; prompt-এ ঘরগুলোর বর্ণনা দিতে বলে | নথিতে পাইনি | Tool-এ JSON Schema parameter; strict mode নথিতে পাইনি | শুধু object, array নয়; finish_reason “length” হলে কাটা পড়েছে |
সারণির একটি শিক্ষা সরাসরি আমাদের schema-তে লাগে। "maximum": 200000 কোনো API decoding-এ মানায়, কোনো API নথি অনুযায়ী নেয়ই না, কোনোটি নেয় কিন্তু “best-effort”. তাই schema একই হলেও নিশ্চয়তা আলাদা, আর নিজের যাচাই সব অবস্থায় লাগে। নিচে অনুরোধের আকার, নথির কাঠামো অনুসরণ করে লেখা; চালানো হয়নি, কারণ এখানে কোনো API key নেই।
# নমুনা (চালানো হয়নি): শুধু body-র মূল অংশ; model-এর নাম নিজে বসান
# OpenAI Responses API
{"model": "…", "input": [{"role": "user", "content": "অর্ডার: …"}],
"text": {"format": {"type": "json_schema", "name": "order",
"strict": true, "schema": ORDER_SCHEMA}}}
# Gemini generateContent
{"contents": [{"parts": [{"text": "অর্ডার: …"}]}],
"generationConfig": {"responseMimeType": "application/json",
"responseJsonSchema": ORDER_SCHEMA}}
# Claude Messages API
{"model": "…", "max_tokens": 300,
"messages": [{"role": "user", "content": "অর্ডার: …"}],
"output_config": {"format": {"type": "json_schema", "schema": ORDER_SCHEMA}}}
# DeepSeek বা Kimi: শুধু JSON mode, schema prompt-এ লিখে
{"model": "…", "response_format": {"type": "json_object"},
"messages": [{"role": "user", "content": "json আকারে দাও: … অর্ডার: …"}]}
একটি খুঁটিনাটি খরচও বদলায়। Python-এর json.dumps নিজে থেকে বাংলাকে র-এর মতো escape করে। Qwen2.5-এর tokenizer-এ (সত্যিকারের run) একটি অর্ডারের JSON escape-সহ ১১২ token, ensure_ascii=False দিলে ৫৩, ফাঁকা জায়গা বাদ দিলে ৪৫; prompt-এ পুরো schema escape-সহ ৪৪৫ token, ছাড়া ২১৮. Prompt-এ schema দেওয়ার সময় তাই ensure_ascii=False. বাংলার token খরচ পর্ব ৪০-এ, টাকার হিসাব পর্ব ৬৫ · Token, খরচ আর latency-তে।
৪ · যাচাই — parse, schema, তারপর ব্যবসার নিয়ম
Model-এর JSON-কে বাইরের ইনপুটের মতো দেখুন, যেমন ওয়েবসাইটের form থেকে আসা ডেটা। ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক কোর্সের পর্ব ২৭-এ request যাচাইয়ের যে নিয়ম, এখানেও তা-ই: আগে বিশ্বাস নয়, আগে পরীক্ষা। পরীক্ষা তিন স্তরে।
- Parse:
json.loadsচলে কি না। সামনে কথা,```jsonবেড়া, বা শেষের কাটা বন্ধনী এখানে ধরা পড়ে। - Schema যাচাই: চাবি, ধরন, enum, সীমা। Python-এ
jsonschemalibrary, বা Pydantic-এর class (FastAPI-র ভিত্তি, ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক কোর্সের পর্ব ৬৯)। - ব্যবসার নিয়ম: যা schema-তে লেখা যায় না বা কঠিন: দাম কি এই পণ্যের সাধারণ সীমায়? জেলা কি মূল লেখায় আসলেই আছে? সংখ্যা ৫০-এর বেশি হলে মানুষ দেখুক।
নিচের কোডে একটি ছোট validator নিজে লেখা, যাতে প্রতিটি নিয়ম চোখে পড়ে। ইনপুট হলো Qwen2.5-0.5B-Instruct-এর সত্যিকারের output, §৭-এর run থেকে বেছে তোলা, হুবহু।
import json
D = ["ঢাকা", "চট্টগ্রাম", "রাজশাহী", "খুলনা", "সিলেট", "বরিশাল", "রংপুর", "ময়মনসিংহ", "কুমিল্লা", "গাজীপুর"]
S = {"district": ("str", D), "product": ("str", None), "quantity": ("int", (1, 50)),
"price_taka": ("int", (1, 200000)), "cod": ("bool", None)}
def check(raw):
try:
obj = json.loads(raw)
except json.JSONDecodeError as e:
return f"parse ভুল: {e.msg} (অক্ষর {e.pos})"
if not isinstance(obj, dict): return "object নয়"
errs = [f"'{k}' নেই" for k in S if k not in obj] + [f"বাড়তি '{k}'" for k in obj if k not in S]
for k, (t, rule) in S.items():
if k not in obj: continue
v = obj[k]
if t == "int" and (isinstance(v, bool) or not isinstance(v, int)): # True-ও int!
errs.append(f"{k}: integer নয় ({v!r})"); continue
if (t == "str" and not isinstance(v, str)) or (t == "bool" and not isinstance(v, bool)):
errs.append(f"{k}: {t} নয় ({v!r})"); continue
if t == "str" and rule and v not in rule: errs.append(f"{k}: তালিকায় নেই ({v!r})")
if t == "str" and not v.strip(): errs.append(f"{k}: খালি")
if t == "int" and not rule[0] <= v <= rule[1]: errs.append(f"{k}: সীমার বাইরে ({v})")
return "; ".join(errs) or "বৈধ"
OUTS = [ # Qwen2.5-0.5B-Instruct-এর সত্যিকারের output
'```json\n{\n "district": "রাজশাহী",\n "product": "সুতির শাড়ি",\n "quantity": 2,\n "price_taka": 1550,\n "cod": true\n}\n```',
'{"district": "চট্টগ্রাম", "product": "ব্লেন্ডার", "quantity": 3500, "price_taka": 3500, "cod": false}',
'{\n "district": "Mymensingh"\n}',
'{"arbor": "Sylhet", "district": "e 3 ta panjabi lagbe", "product": "panjabi lagbe", "quantity": "2700", "price_taka": "2700"}',
'{"district": "রাজশাহী", "product": "সুতির শাড়ি", "quantity": 2, "price_taka": 1550, "cod": false}']
for i, raw in enumerate(OUTS, 1):
print(f"[{i}] {check(raw)}")আউটপুট (পাতার Python-এ চালানো):
[1] parse ভুল: Expecting value (অক্ষর 0)
[2] quantity: সীমার বাইরে (3500)
[3] 'product' নেই; 'quantity' নেই; 'price_taka' নেই; 'cod' নেই; district: তালিকায় নেই ('Mymensingh')
[4] 'cod' নেই; বাড়তি 'arbor'; district: তালিকায় নেই ('e 3 ta panjabi lagbe'); quantity: integer নয় ('2700'); price_taka: integer নয় ('2700')
[5] বৈধ
পাঁচটি সত্যিকারের উত্তর, পাঁচ রকম ফল। [১] schema-সহ prompt-এর উত্তর, দেখতে নিখুঁত, কিন্তু ```json বেড়ায় মোড়া, তাই অক্ষর ০-তেই parse ব্যর্থ। [২] few-shot-এর উত্তর, ৩৫০০ টাকার দামটি quantity-তেও বসেছে; সীমা (১–৫০) ধরে ফেলল। [৩] retry-র মাঝের একটি উত্তর, চারটি চাবিই নেই। [৪] সাধারণ prompt-এর উত্তর (বেড়া সরিয়ে এক লাইনে আনা): বানানো চাবি “arbor”, জেলার ঘরে অর্ডারের কথা, আর সংখ্যা লেখা হিসেবে। [৫] সব নিয়ম মেনে “বৈধ”, অথচ অর্ডারটি ক্যাশ অন ডেলিভারি, cod হওয়া উচিত ছিল true. Validator এই ভুল দেখতে পায় না।
কোডে isinstance(v, bool) আগে পরীক্ষা করা হয়েছে, কারণ Python-এ True একটি int; না করলে "quantity": true ১ হিসেবে পার পেত।
আসল কাজে নিজে validator না লিখে পরীক্ষিত library নিন। একই ভাঙা JSON (এবার হাতে বানানো, তিনটি ভুলসহ, ফোন নম্বরটি নকল) Pydantic আর jsonschema-কে দেওয়া হলো। কোড একটু ছোট করে দেখানো; আউটপুট সত্যিকারের run-এর:
class Order(BaseModel): # pydantic 2.14.3
model_config = ConfigDict(extra="forbid", strict=True)
district: Literal["ঢাকা", "চট্টগ্রাম", "রাজশাহী", …] # ১০টি জেলা
product: str = Field(min_length=1)
quantity: int = Field(ge=1, le=50)
price_taka: int = Field(ge=1, le=200000)
cod: bool
raw = '{"district": "Rajshahi", "product": "শাড়ি", "quantity": "২",
"price_taka": 1550, "cod": true, "phone": "01XXXXXXXXX"}'
Order.model_validate_json(raw)
~/bazarghar-dl$ uv run python desk_validate.py # সত্যিকারের run; লিংকের লাইন বাদ
3 validation errors for Order
district
Input should be 'ঢাকা', 'চট্টগ্রাম', 'রাজশাহী', 'খুলনা', 'সিলেট', 'বরিশাল',
'রংপুর', 'ময়মনসিংহ', 'কুমিল্লা' or 'গাজীপুর' [type=literal_error,
input_value='Rajshahi', input_type=str]
quantity
Input should be a valid integer [type=int_type, input_value='২', input_type=str]
phone
Extra inputs are not permitted [type=extra_forbidden, input_value='01XXXXXXXXX', input_type=str]
--- jsonschema
[] additionalProperties | Additional properties are not allowed ('phone' was unexpected)
['district'] enum | 'Rajshahi' is not one of ['ঢাকা', 'চট্টগ্রাম', 'রাজশাহী', 'খুলনা', 'সি
['quantity'] type | '২' is not of type 'integer'
দুই library একই তিনটি ভুল ধরেছে, আর ভুলগুলো পরের ধাপে কাজে লাগার মতো স্পষ্ট: কোন চাবি, কী চাওয়া, কী পাওয়া। strict=True না দিলে Pydantic নিজে থেকে কিছু ধরন বদলে নেয় (lax mode). একই version-এ আলাদা পরীক্ষায় (সত্যিকারের run) lax mode-এ "2" হলো 2, true হলো quantity 1, আর cod-এ "yes" হলো True; অথচ বাংলা "২" বাতিল, যদিও Python-এর নিজের int("২") 2 দেয়। এমন নীরব রূপান্তরে ভুল ঢাকা পড়ে। এই পর্বে নিয়ম হলো: রূপান্তর নিজে, লিখিত নিয়মে (§৫), আর যাচাই কড়া। Pydantic class থেকে model_json_schema() দিয়ে JSON Schema-ও বানানো যায়, যা API-তে পাঠানো চলে।
৫ · বাংলা অঙ্ক, দাম আর জেলার নাম — এক রূপে আনা
বাংলাদেশের অর্ডারে একই দাম অনেক রূপে আসে: “১,৫৫০ টাকা”, “৳১৫৫০”, “1,550 tk”, “দেড় হাজার”. লাখের হিসাবে কমার জায়গাও আলাদা: “১,২৫,০০০”. জেলাও আসে “Sylhet”, “সিলেটে”, “Cumilla” রূপে। Model-কে schema-তে integer চাইলেও ছোট model প্রায়ই "১,৫৫০" লেখা দেয়। দুটি পথ আছে: model-কে আরও চাপ দেওয়া, অথবা model-এর লেখা মান নিজের কোডে normalise করা। দ্বিতীয়টি সস্তা আর পরীক্ষাযোগ্য। পর্ব ৫৮-এ exact match-এর আগে একই ধরনের নিয়ম দেখেছেন; এবার লক্ষ্য মাপা নয়, ডেটা ঠিক রাখা।
মূল নিয়ম: যা নিশ্চিতভাবে বোঝা যায়, শুধু তা-ই বদলান; বাকিগুলোতে None, যাতে মানুষ দেখে। “৯৯.৫০ টাকা” পূর্ণসংখ্যা নয়; চুপচাপ ৯৯ বা ১০০ বানানো ঠিক নয়।
import re, unicodedata
DIG = str.maketrans("০১২৩৪৫৬৭৮৯", "0123456789")
WORD = {"দেড়": 1.5, "আড়াই": 2.5, "এক": 1, "দুই": 2, "তিন": 3}
SCALE = {"হাজার": 1000, "শ": 100, "শো": 100, "লাখ": 100000}
def price_to_int(s):
"""'১,৫৫০ টাকা' -> 1550; না বুঝলে None (আন্দাজ নয়)"""
s = unicodedata.normalize("NFC", str(s)).translate(DIG).lower()
s = re.sub(r"(৳|টাকা|taka|tk\.?|/-)", " ", s).replace(",", "").strip()
m = re.fullmatch(r"(\d+(?:\.\d+)?)\s*(হাজার|শ|শো|লাখ)?", s)
if m:
v = float(m.group(1)) * SCALE.get(m.group(2), 1)
else:
m = re.fullmatch(r"(দেড়|আড়াই|এক|দুই|তিন)\s*(হাজার|শ|শো|লাখ)", s)
if not m:
return None
v = WORD[m.group(1)] * SCALE[m.group(2)]
return int(v) if v == int(v) else None # পয়সা থাকলে মানুষ দেখুক
TESTS = ["১,৫৫০ টাকা", "৳২,০০০", "1,800 tk", "১,২৫,০০০/-", "১৫৫০.০০",
"দেড় হাজার", "৩.৫ হাজার", "আড়াইশো", "৯৯.৫০ টাকা", "দাম জানি না", 1550]
for t in TESTS:
print(f"{str(t):>14} → {price_to_int(t)}")
# জেলার নামও এক রূপে: model 'Sylhet' বা 'সিলেটে' লিখতে পারে
N = lambda s: unicodedata.normalize("NFC", s).strip().lower()
DISTRICTS = [N(d) for d in ["ঢাকা", "চট্টগ্রাম", "রাজশাহী", "সিলেট", "কুমিল্লা"]]
ALIAS = {"sylhet": "সিলেট", "comilla": "কুমিল্লা", "cumilla": "কুমিল্লা", "dhaka": "ঢাকা"}
def district(s):
s = N(ALIAS.get(N(s), s))
for suf in ["", "তে", "য়", "ে", "এ"]: # রাজশাহীতে, ঢাকায়, সিলেটে
if s.endswith(N(suf)) and s[:len(s) - len(N(suf))] in DISTRICTS:
return s[:len(s) - len(N(suf))]
return None
for d in ["Sylhet", "সিলেটে", "ঢাকায়", "Cumilla", "রাজশাহীতে", "নারায়ণগঞ্জ"]:
print(f"{d:>10} → {district(d)}")আউটপুট (পাতার Python-এ চালানো):
১,৫৫০ টাকা → 1550
৳২,০০০ → 2000
1,800 tk → 1800
১,২৫,০০০/- → 125000
১৫৫০.০০ → 1550
দেড় হাজার → 1500
৩.৫ হাজার → 3500
আড়াইশো → 250
৯৯.৫০ টাকা → None
দাম জানি না → None
1550 → 1550
Sylhet → সিলেট
সিলেটে → সিলেট
ঢাকায় → ঢাকা
Cumilla → কুমিল্লা
রাজশাহীতে → রাজশাহী
নারায়ণগঞ্জ → None
প্রতিটি রূপ এক নিয়মে: বাংলা অঙ্ক ইংরেজিতে (str.translate), ৳, টাকা, tk আর /- বাদ, কমা বাদ, তারপর পুরো লেখাটি সংখ্যা কি না re.fullmatch দিয়ে। “১,২৫,০০০”-এর লাখের হিসাবের কমাগুলোও ঠিক পড়া গেল। “৯৯.৫০ টাকা” আর “দাম জানি না” None: অনুমান নয়, মানুষ দেখবে। জেলার ক্ষেত্রে শুধু জানা alias আর বিভক্তি (-তে, -য়, -এ) সরানো; “নারায়ণগঞ্জ” তালিকায় নেই বলে None, যদিও এটি একটি সত্যিকারের জেলা। তালিকা বাড়ানো একটি ব্যবসার সিদ্ধান্ত, কোডের নয়।
তুলনার আগে দুই পাশে NFC কেন? “য়” একটি code point বা “য” + নুকতা, দুইভাবেই লেখা যায়; চোখে এক, string-এ আলাদা (পর্ব ৪০)।
৬ · Constrained decoding — প্রতি ধাপে অচল token বাদ
এ পর্যন্ত যাচাই হয়েছে উত্তর আসার পরে। এবার উল্টো প্রশ্ন: উত্তর লেখার সময়েই কি ভুল আটকানো যায়? পর্ব ৫০-এ দেখেছেন, প্রতি ধাপে model পুরো vocabulary-র logit দেয়, তারপর একটি token বাছা হয়। Constrained decoding-এ বাছাইয়ের ঠিক আগে একটি token mask বসে: এখন পর্যন্ত লেখা অংশের পরে ব্যাকরণ যে token-গুলো চলতে দেয় না, তাদের logit −∞ করা হয়। Softmax-এর পর তাদের সম্ভাবনা শূন্য; বাকিদের সম্ভাবনা আবার যোগফল ১ হয়।
ব্যাকরণটি আসে schema থেকে। {"district": " লেখার পরে শুধু enum-এর দশটি জেলার শুরু চলতে পারে; "price_taka": -এর পরে শুধু অঙ্ক, কারণ ধরন integer. এই নিয়মগুলো একটি finite-state machine বা context-free grammar হিসেবে লেখা যায় (JSON-এর বন্ধনী ভেতরে ভেতরে বসে বলে পুরো JSON-এর জন্য দ্বিতীয়টি লাগে)। কঠিন অংশ token: “রাজশাহী” একটি token নয়, Qwen-এর tokenizer-এ কয়েকটি byte-টুকরো (পর্ব ৪০)। তাই প্রতি ধাপে দেখতে হয়, কোন token-এর অক্ষরগুলো যোগ করলে লেখাটি তখনো কোনো বৈধ উত্তরের শুরু থাকে। lm-format-enforcer আর outlines-এর মতো library এই কাজটি করে; Willard ও Louf (২০২৩) regex আর grammar থেকে এই হিসাব আগে থেকে index করার একটি পদ্ধতি প্রকাশ করেন, যা outlines-এর ভিত্তি।
নিচের stepper-এর সংখ্যা সত্যিকারের run থেকে: Qwen2.5-0.5B-Instruct, প্রথম অর্ডার, schema-সহ prompt, আর lm-format-enforcer-এর mask.
| ধাপ | |
| লেখার শেষ | |
| অনুমোদিত token | |
| model-এর শীর্ষ | |
| অনুমোদিতদের মোট p | |
| বাছাই |
- প্রসঙ্গ. নিজের লেখা mask-এর প্রথম সংস্করণে কাঠামোর লেখা
"cod":ফাঁকা জায়গাসহ বসানো হয়েছিল। এখন schema-মতে চলে শুধুtrueবাfalse, বা তাদের শুরুর টুকরো: ৮টি token. - Mask-এর আগে. Model-এর নিজের পছন্দ “1” (০.৬৬৯) আর “0” (০.২৩০), দুটিই অচল। “␣true” (ফাঁকা জায়গাসহ একটি token) ০.০৫১, সেটিও অচল, কারণ ফাঁকা জায়গাটি আগেই বসে গেছে। অনুমোদিত সব token মিলে সম্ভাবনা মাত্র ০.০০০২।
- Mask-এর পরে. বাকিদের logit −∞; softmax আবার ভাগ করে: “true” ০.৮৩৬. উত্তর বৈধ, কিন্তু বাছাই হয়েছে model-এর মোট সম্ভাবনার ০.০২%-এর ভেতর থেকে। এটি token-সীমানার সমস্যা: যে ভাঙনে model অভ্যস্ত, mask সেটি বন্ধ করে দিয়েছে।
- ফাঁকা জায়গা ছেড়ে. সংশোধিত mask-এ কাঠামো থামে
"cod":-এ, আর ফাঁকা জায়গাটি model নিজে লেখে। এবার “␣true” ০.৫৬৭ আর “␣false” ০.৪৩১ দুটিই অনুমোদিত, মোট ০.৯৯৮৬. Model-এর পছন্দ আর নিয়ম এখন একমত। - অন্য ঝুঁকি. অর্ডার ৪-এ (খুলনা, মোট ৬৫০ টাকা) model শুরুতেই “5” বেছেছে। “5000”-এর পরে “0” (০.৫০০) আর শেষ করার “,\n” (০.৪৯৮) প্রায় সমান; greedy নিল “0”, ফল 50000. Mask আকার রক্ষা করে, মান নয়।
এবার পুরো চক্রটি একটি খেলনা model-এ নিজে চালান। Vocabulary ১৫টি token, ব্যাকরণ একটি ছোট state machine, আর “model” হলো seed দেওয়া এলোমেলো logit, যার কয়েকটি ভুল token-এর দিকে ঝোঁক (Sylhet, বাংলা ৩, “জি, ”)।
import numpy as np, json
# ছোট vocabulary; একটি token একাধিক অক্ষরও হতে পারে, আসল tokenizer-এর মতো
V = ['{', '}', '"district": ', '"qty": ', ', ', '"ঢাকা"', '"সিলেট"',
'"Dhaka"', '"Sylhet"', '1', '2', '৩', '"3"', 'জি, ', '<eos>']
# ব্যাকরণ: { "district": ENUM , "qty": DIGIT+ } <eos>
ENUM, DIG = {'"ঢাকা"', '"সিলেট"'}, {'1', '2'}
G = {"start": ({'{'}, "key1"), "key1": ({'"district": '}, "val1"),
"val1": (ENUM, "comma"), "comma": ({', '}, "key2"),
"key2": ({'"qty": '}, "num"), "num": (DIG, "num+"),
"num+": (DIG | {'}'}, None), "end": ({'<eos>'}, "done")}
def nxt(state, tok): # অবস্থা বদলের নিয়ম
if state == "num+":
return "num+" if tok in DIG else "end"
return G[state][1]
rng = np.random.default_rng(62)
bias = {'"Sylhet"': 2.0, '৩': 2.5, 'জি, ': 1.5} # খেলনা model-এর বদভ্যাস
state, free, out = "start", [], []
print(f"{'ধাপ':>3} {'অবস্থা':7} {'অনুমোদিত':>8} {'mask ছাড়া':12} mask সহ")
for t in range(9):
logit = rng.normal(0, 1, len(V)) + np.array([bias.get(v, 0) for v in V])
allowed = np.array([v in G[state][0] for v in V])
logit[allowed] += 1.0 # ঠিক token-ও বেশ সম্ভাব্য
best_free = V[int(np.argmax(logit))]
tok = V[int(np.argmax(np.where(allowed, logit, -np.inf)))] # −∞ = বাদ
print(f"{t:>3} {state:7} {allowed.sum():>8} {best_free!r:12} {tok!r}")
free.append(best_free); out.append(tok)
state = nxt(state, tok)
if state == "done": break
for name, toks in (("mask ছাড়া", free), ("mask সহ", out)):
s = "".join(toks).replace("<eos>", "")
try: json.loads(s); ok = "বৈধ JSON"
except ValueError: ok = "JSON নয়"
print(f"{name}: {s} → {ok}")আউটপুট (পাতার Python-এ চালানো):
ধাপ অবস্থা অনুমোদিত mask ছাড়া mask সহ
0 start 1 '৩' '{'
1 key1 1 '"ঢাকা"' '"district": '
2 val1 2 '"Sylhet"' '"ঢাকা"'
3 comma 1 '৩' ', '
4 key2 1 '"Sylhet"' '"qty": '
5 num 2 '৩' '2'
6 num+ 3 '"Sylhet"' '1'
7 num+ 3 '৩' '}'
8 end 1 'জি, ' '<eos>'
mask ছাড়া: ৩"ঢাকা""Sylhet"৩"Sylhet"৩"Sylhet"৩জি, → JSON নয়
mask সহ: {"district": "ঢাকা", "qty": 21} → বৈধ JSON
দুটি কলাম একই অবস্থায় দাঁড়িয়ে model-এর সেরা token দেখায়। Mask ছাড়া সেরা token প্রায় প্রতি ধাপে অচল: শুরুতেই বাংলা “৩”, পরে “Sylhet”, শেষে “জি, ”. তাদের জোড়া দিলে JSON-ই হয় না। Mask সহ প্রতি ধাপে অনুমোদিত তালিকা থেকে সবচেয়ে বড় logit বাছা হয়েছে, তাই ফল বৈধ। কিন্তু qty এসেছে 21: “num+” অবস্থায় আরেকটি অঙ্ক আর } দুটিই চলে, আর খেলনা model অঙ্ক বেছেছে। ব্যাকরণ আকার ঠিক রাখে; সংখ্যাটি সঠিক কি না, তা বলে না। আসল library একই কাজ করে, শুধু vocabulary হাজারো token-এর আর ব্যাকরণ পুরো JSON Schema থেকে বানানো।
৭ · সত্যিকারের run — ৩০টি অর্ডারে পদ্ধতিগুলোর তুলনা
এবার একটি ছোট পরীক্ষা। ৩০টি হাতে লেখা কাল্পনিক বাজারঘর অর্ডার: বাংলা, Banglish আর মিশ্র; দাম “১,৫৫০”, “৳২,০০০”, “1,800 tk” রূপে; প্রতিটির সঠিক উত্তর আগে থেকে লেখা। Model Qwen2.5-0.5B-Instruct, CPU-তে ৩টি thread, greedy decoding (do_sample=False, repetition_penalty=1.0), সর্বোচ্চ ১২০টি নতুন token. পদ্ধতিগুলো:
- সাধারণ prompt: “district, product, quantity, price_taka আর cod বের করে JSON দাও।” আরেকটি ভাষ্যে একই অনুরোধ অন্য শব্দে।
- Schema-সহ prompt: §২-এর পুরো schema prompt-এ, সঙ্গে “শুধু JSON”; এরও দ্বিতীয় একটি ভাষ্য।
- Few-shot: schema-সহ prompt, আগে দুটি আলাদা অর্ডারের উদাহরণ-উত্তর (এই ৩০টির বাইরের)।
- lm-format-enforcer: library-র JSON Schema parser, transformers-এর
prefix_allowed_tokens_fnদিয়ে। - নিজের token mask: §৬-এর মতো, কিন্তু UTF-8 byte ধরে (নিচে কারণ)। দুটি সংস্করণ: সরল, আর token-সীমানা-সচেতন।
- Schema + মেরামত + retry: §৮-এ।
~/bazarghar-dl$ uv run python summary.py res_q05.jsonl v3.jsonl # সত্যিকারের run-এর ফল
condition strict defenced schema-valid [95% CI] all-5-fields [95% CI] mean s
plain 0.00 0.83 0.00 0.00 5.07
plain2 0.00 0.00 0.00 0.00 7.31
schema 0.00 1.00 0.80 [0.63, 0.93] 0.20 [0.07, 0.37] 6.17
schema2 0.00 0.17 0.07 [0.00, 0.17] 0.00 9.82
fewshot 1.00 1.00 0.93 [0.83, 1.00] 0.23 [0.10, 0.40] 9.08
lm-format-enf. 0.03 0.03 0.03 [0.00, 0.10] 0.00 3.21
mask (simple) 1.00 1.00 1.00 0.23 [0.10, 0.40] 5.24
mask (boundary) 1.00 1.00 1.00 0.30 [0.13, 0.47] 4.35
per field (schema / fewshot / mask-boundary):
district 0.80/0.93/0.83 product 0.43/0.57/0.43 quantity 0.90/0.93/0.90
price_taka 0.73/0.93/0.67 cod 0.57/0.43/0.73
paired vs schema (all-5-fields): fewshot +0.03 [-0.20, +0.27] mask(boundary) +0.10 [+0.00, +0.23]
কলামের অর্থ. strict: model-এর লেখা হুবহু json.loads-এ চলে কি না। defenced: ```json বেড়া বা আশপাশের কথা সরিয়ে। schema-valid: বেড়া সরানোর পর schema পার। all-5-fields: পাঁচটি ঘরই সঠিক (জেলার নাম §৫-এর নিয়মে এক রূপে এনে)। CI হলো ১০,০০০ বার bootstrap, অর্ডার ধরে (পর্ব ৫৮)। শুধু ৩০টি অর্ডার, একটি ছোট model আর একটি run; বড় model বা hosted পণ্যে সংখ্যাগুলো অন্যরকম হবে।
১. বেড়া. Schema-prompt-এর ৩০টি উত্তরের ৩০টিই ```json বেড়ায়, তাই হুবহু parse ০. এক লাইনের মেরামতে ১.০০. Few-shot-এ উদাহরণ-উত্তর বেড়া ছাড়া ছিল, model সেই ধাঁচ নিয়েছে: হুবহু parse ১.০০.
২. শব্দের ভাষ্য. একই অনুরোধ অন্য শব্দে লিখতেই schema-বৈধতা ০.৮০ থেকে ০.০৭; সাধারণ prompt-এর দ্বিতীয় ভাষ্যে model JSON-এর বদলে অনুরোধটিই আবার লিখেছে। ছোট model-এ prompt-এর সামান্য বদল বড় ফারাক আনে (পর্ব ৬০ · Prompt-এর গঠন-এ এ নিয়ে বিস্তারিত); তাই একটি ভাষ্যের ফল দিয়ে “পদ্ধতিটি কাজ করে” বলা যায় না।
৩. Library ভাঙল বাংলায়. lm-format-enforcer ৩০টির ২২টিতে “constraint is unsatisfiable” বলে থেমে গেছে, ৭টিতে product-এর ঘরে tab-এর সারি লিখে অবৈধ JSON, ১টি বৈধ। কারণ: library অক্ষর ধরে কাজ করে, আর Qwen-এর tokenizer একটি বাংলা অক্ষরকে (UTF-8-এ ৩ byte) প্রায়ই দুটি token-এ ভাঙে। আধা-অক্ষরের token decode করলে “�”, তাই parser “ময়মনসিংহ”-এর “ময”-এর পরে নুকতা লেখার কোনো বৈধ token পায় না। এটি এই version-এ এই tokenizer-এর ফল; অন্য library বা version-এ আলাদা হতে পারে। তাই নিজের mask লেখা হয়েছে byte ধরে: প্রতিটি token-এর আসল byte দেখে বিচার, “এর পরে লেখাটি কি কোনো বৈধ উত্তরের শুরু থাকে?”
৪. বৈধ বনাম সঠিক. নিজের mask-এ schema-বৈধতা ১.০০, schema-prompt-এর চেয়ে +০.২০ [+০.০৭, +০.৩৭], জোড়ায় মাপা। কিন্তু পাঁচ ঘরই সঠিক মাত্র ০.৩০; schema-prompt-এর সঙ্গে পার্থক্য +০.১০, CI শূন্য ছুঁয়েছে, তাই ৩০টি অর্ডারে নিশ্চিত নয়। Few-shot-এর +০.০৩-ও তা-ই। দামে mask (০.৬৭) few-shot-এর (০.৯৩) পিছনে; §৬-এ দেখেছেন কেন, mask সংখ্যার আকার ঠিক রাখে, সংখ্যাটি নয়। আর cod: ৩০টির ১৭টি ক্যাশ অন ডেলিভারি, তাই “সবসময় true” লিখলেই ০.৫৭; schema-prompt ঠিক সেখানেই।
৫. সীমানা আর সময়. সরল mask-এ শুধু cod-এর আগের ফাঁকা জায়গাটি model-কে ছেড়ে দিলে cod ০.৫৭ থেকে ০.৭০ (আলাদা run); ",\n-এর মতো সীমানা-পার token-ও চলতে দিলে ০.৭৩. Mask-এ সময় কম লেগেছে, কারণ কাঠামোর লেখা (চাবির নাম, বন্ধনী) model-কে না জিজ্ঞেস করে একসঙ্গে বসানো হয়: model বেছেছে গড়ে ২৬টি token, schema-prompt-এ ৫৯টি। Few-shot-এ prompt লম্বা, তাই ধীর।
নিজের mask-এর মূল অংশ, জেলার ঘরের জন্য (একটু ছোট করে দেখানো; পুরো mask প্রায় ১৫০ লাইন, বাকি ঘরের নিয়ম একই ধাঁচের):
TB = [token_bytes(i) for i in range(len(tok))] # প্রতিটি token-এর আসল UTF-8 byte
BY = group_by_bytes(TB) # byte → token id-এর তালিকা
def allowed_enum(cur): # cur: এই ঘরে এ পর্যন্ত লেখা byte
out = set()
for e in ENUM_BYTES: # '"রাজশাহী"' + পরের কাঠামো, byte-এ
if e.startswith(cur):
rest = e[len(cur):]
for k in range(1, len(rest) + 1): # বাকি অংশের যেকোনো শুরু একটি token হলে চলবে
out.update(BY.get(rest[:k], []))
return out
masked = torch.full_like(logits, float("-inf"))
masked[ids] = logits[ids]; t = int(masked.argmax()) # তারপর greedy বাছাই৮ · Validate–repair–retry — কখন আবার জিজ্ঞাসা, কখন মানুষ
যাচাই ব্যর্থ হলে তিনটি পথ, খরচের ক্রমে: (১) নিজে মেরামত, যেমন বেড়া সরানো, model না ডেকেই; (২) আবার জিজ্ঞাসা, আগের উত্তর আর ভুলের বার্তাসহ, যাতে model জানে কী ভাঙল; (৩) সীমা পার হলে মানুষের কাছে. পুরো loop-টিকে বলে repair-retry loop. সীমা না থাকলে খরচ আর অপেক্ষা থামে না; Instructor-এর মতো library-ও retry-এর সংখ্যা একটি parameter হিসেবে রাখে।
| ধাপ | |
| model-এর লেখা | |
| যাচাই | |
| model ডাক | |
| token, সময় |
- চেষ্টা ১. Schema-সহ prompt; উত্তর
```jsonবেড়ায় মোড়া, তাইjson.loadsপ্রথম অক্ষরেই ব্যর্থ। এই model-এ schema-prompt-এর ৩০টি উত্তরের ৩০টিই এভাবে বেড়ায় এসেছে। - নিজে মেরামত. Model আবার না ডেকে কোড প্রথম
{থেকে শেষ}পর্যন্ত তুলে নেয়। এবার parse হয়, কিন্তু schema বলে “Mymensingh” তালিকায় নেই। বেড়ার সমস্যা বিনা খরচে মিটল, জেলার সমস্যা মিটল না। - আবার জিজ্ঞাসা. আগের উত্তর আর ভুলের বার্তা (“district: 'Mymensingh' is not one of […]”) জুড়ে দ্বিতীয় অনুরোধ। Model জেলা ঠিক না করে শুধু জেলার চাবিটি ফেরত দিল; এবার product-সহ চারটি চাবি নেই। উত্তর আগের চেয়েও খারাপ।
- চেষ্টা ৩. “product নেই” বার্তা পেয়ে model প্রথম উত্তরটিই হুবহু ফিরিয়ে দিল, আবার “Mymensingh”. Greedy decoding-এ একই প্রসঙ্গে একই ভুল ফিরে আসা স্বাভাবিক।
- সীমা শেষ. দুবার retry-র সীমা পূর্ণ; অর্ডারটি মানুষের কাছে যায়। খরচ তিনটি ডাক, ১১৭ token, প্রায় ১৫ সেকেন্ড, আর লাভ শূন্য। অথচ “Mymensingh” → “ময়মনসিংহ” একটি ছোট alias-তালিকাই ঠিক করত (§৫)।
এবার পুরো ৩০টি অর্ডারে। একই schema-prompt দুইভাবে চালানো হয়েছে, সর্বোচ্চ দুবার retry: একবার প্রতিটি চেষ্টার পরে আগে নিজে মেরামত, একবার মেরামত ছাড়া, শুধু আবার জিজ্ঞাসা। নিচের কোডে সেই সত্যিকারের চেষ্টাগুলোর ফল ক্রমমতো রাখা; কোড শুধু গোনে, retry-এর সীমা ০, ১ আর ২ হলে কী হতো।
# সত্যিকারের run: ৩০টি অর্ডার, schema-prompt, প্রতিটি চেষ্টার ফল ক্রমমতো।
# P = JSON-ই নয় (বেড়ায় মোড়া), F = বেড়া সরালেই বৈধ, S = বেড়া সরালেও schema ভুল
WITH_REPAIR = ("F F F F SSS F F F F F SSS F F F F F F SSS F F SSS SSS SSS "
"F F F F F F F").split() # প্রতিটি চেষ্টার পরে আগে নিজে মেরামত
NO_REPAIR = ["PPP"] * 30 # মেরামত ছাড়া: তিনবারই হুবহু একই উত্তর
FIRST = [65, 56, 56, 63, 65, 58, 54, 57, 58, 67, 52, 51, 54, 61, 61,
59, 63, 50, 60, 62, 60, 58, 49, 60, 69, 57, 73, 52, 76, 57] # প্রথম চেষ্টার token
LATER = {4: [65, 65], 10: [16, 52], 17: [17, 50], 20: [60, 60],
21: [17, 58], 22: [16, 49]} # মেরামতসহ পথে পরের চেষ্টার token
def simulate(log, repair, max_retry):
ok = calls = tokens = human = 0
for i, tries in enumerate(log):
toks = [FIRST[i]] + (LATER.get(i, [FIRST[i]] * 2) if repair else [FIRST[i]] * 2)
for k, t in enumerate(tries[:max_retry + 1]):
calls += 1; tokens += toks[k]
if t == "F" and repair:
ok += 1; break
else:
human += 1 # সীমা শেষ: মানুষের কাছে
return ok, calls, tokens, human
print("নীতি retry বৈধ ডাক token মানুষের কাছে")
for name, log, rep in (("মেরামত ছাড়া", NO_REPAIR, False), ("আগে মেরামত", WITH_REPAIR, True)):
for r in (0, 1, 2):
ok, c, t, h = simulate(log, rep, r)
print(f"{name:14} {r:5} {ok:4} {c:4} {t:6} {h:6}")আউটপুট (পাতার Python-এ চালানো):
নীতি retry বৈধ ডাক token মানুষের কাছে
মেরামত ছাড়া 0 0 30 1783 30
মেরামত ছাড়া 1 0 60 3566 30
মেরামত ছাড়া 2 0 90 5349 30
আগে মেরামত 0 24 30 1783 6
আগে মেরামত 1 24 36 1974 6
আগে মেরামত 2 24 42 2308 6
শেষ সারিগুলো সত্যিকারের দুটি run-এর মোট হিসাবের সঙ্গে মেলে: মেরামত ছাড়া ৯০টি ডাক আর ৫,৩৪৯ token, মেরামতসহ ৪২টি ডাক আর ২,৩০৮ token. দুটি শিক্ষা। এক, মেরামত ছাড়া retry এখানে পুরো অপচয়: ভুলের বার্তা “Expecting value: line 1 column 1” পেয়েও model তিনবারই হুবহু একই বেড়া-মোড়া উত্তর দিয়েছে; ৩০টির ০টি বৈধ, খরচ তিনগুণ। দুই, মেরামতসহ পথে ২৪টি অর্ডার প্রথম ডাকেই বিনা খরচের মেরামতে বৈধ; বাকি ৬টি schema-ভুল (Mymensingh, Khulna, সংখ্যার ঘরে দাম) দুবার retry-তেও সারেনি, শুধু ১২টি বাড়তি ডাক খরচ হয়েছে। এই ছোট model-এ তাই retry-এর সীমা ০ রাখাই সবচেয়ে সস্তা ছিল।
এটি সাধারণ নিয়ম নয়। বড় model ভুলের বার্তা পড়ে প্রায়ই ঠিক করে নিতে পারে, তাই retry-এর মূল্য নিজের model আর ডেটায় এভাবেই মাপুন: কতগুলো আসলে সারল, কত ডাক আর token গেল। আর যে ভুল ধাঁচে বারবার আসে (ইংরেজি জেলার নাম), তার সমাধান retry নয়, §৫-এর মতো normalisation বা few-shot উদাহরণ।
৯ · AI-এর যুগে — structured output-এর কোড আর দাবি যাচাই
Extraction-এর কোড AI assistant দ্রুত লিখে দেয়। ভুলগুলো সাধারণত ক্র্যাশ করে না; চুপচাপ ভুল ডেটা database-এ ঢোকে।
| AI প্রায়ই যা করে | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| পুরনো বা বানানো parameter, যেমন অন্য কোম্পানির নাম অন্যটিতে | API-গুলোর নাম আলাদা আর বদলায় (§৩-এর সারণি) | নিজের version-এর নথি খুলে মেলানো; একটি ছোট অনুরোধে পরীক্ষা |
json.loads সফল হলেই “ঠিক আছে” | বৈধ JSON মানে schema মানা নয়, schema মানা মানে সঠিক নয় | তিন স্তরের যাচাই; field accuracy মাপা |
isinstance(v, int) দিয়ে integer পরীক্ষা | Python-এ True-ও int | Bool আগে বাদ; বা jsonschema/Pydantic strict |
| Retry-এর loop সীমা ছাড়া | খরচ আর সময় অসীম হতে পারে | সর্বোচ্চ ২–৩ বার, তারপর মানুষ |
“১,৫৫০”-কে int()-এ দেওয়া বা কমা মুছে আন্দাজ | “৯৯.৫০” বা “দেড় হাজার”-এ চুপচাপ ভুল | লিখিত নিয়ম আর test; না বুঝলে None |
| একটি সফল উদাহরণ দেখে “model সব পারে” | ৩০টির মতো set-এও CI চওড়া | Eval set, n আর CI (পর্ব ৫৮) |
| Schema-তে দরকারের বেশি ঘর (ফোন, নাম) | ব্যক্তিগত তথ্য অকারণে জমে | শুধু দরকারি ঘর; additionalProperties: false |
কোড চাওয়ার সময় যাচাইও চেয়ে নিন। গ্রাহকের আসল অর্ডার, ফোন নম্বর বা ঠিকানা কোনো hosted assistant-এর prompt-এ দেবেন না; বানানো উদাহরণ দিন।
Structured output-এর চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১API-তে schema-বাঁধা output চালু থাকলে নিজের কোডে আবার যাচাই কেন?
কারণ নিশ্চয়তাটি সরু, আর প্রতিটি নথিই তার সীমা লিখে দেয়। প্রথম, keyword-এর সমর্থন। §৩-এর সারণিতে দেখেছেন, Anthropic-এর নথি অনুযায়ী minimum আর maximum সমর্থিত নয়; Gemini-র নথি বলে minLength-এর মতো কিছু keyword decoding-এ মানা হয় না, আর নিজে যাচাই করতে বলে; xAI কিছু keyword-কে “best-effort” বলে। একই schema পাঠালেও কোন নিয়ম আসলে আটকানো হলো, তা API-ভেদে আলাদা।
দ্বিতীয়, ব্যতিক্রমের পথ। OpenAI আর Anthropic দুজনের নথিই বলে, model প্রত্যাখ্যান (refusal) করলে বা token-এর সীমায় (max_output_tokens, max_tokens) থামলে উত্তর schema না-ও মানতে পারে। DeepSeek-এর JSON mode-এর নথি সতর্ক করে, উত্তর শুধু ফাঁকা জায়গা হতে পারে। Kimi-র নথি finish_reason “length” দেখতে বলে। এগুলো ধরার দায়িত্ব আপনার কোডের।
তৃতীয়, আর সবচেয়ে বড়: আকার আর অর্থ আলাদা। Gemini-র নথি স্পষ্ট লেখে, output বৈধ JSON হলেও মান ভুল হতে পারে। আমাদের run-এ constrained decoding-এ বৈধতা বেড়েছে, কিন্তু সব ঘর ঠিক হওয়ার হার অনেক কম ছিল (§৭)। “রাজশাহী”-র অর্ডারে “ঢাকা” schema পার হয়ে যায়।
চতুর্থ, পরিবর্তন। API-র নিয়ম, model-এর version আর আপনার schema সময়ের সঙ্গে বদলায়। নিজের validator একটি স্থির চুক্তি, যা model বদলালেও একই থাকে, আর regression ধরা পড়ে (পর্ব ৬৬ · Prompt পরীক্ষা)।
মূল উপলব্ধি: API-র নিশ্চয়তা আকারের, তাও সব keyword-এর নয়। নিজের যাচাই সেই চুক্তি, যার ওপর database ভরসা করে।
প্র ০২Token mask কি model-এর উত্তরের মান কমাতে পারে?
পারে, আর কারণটি গণিতে দেখা যায়। Mask প্রতি ধাপে অনুমোদিত token-গুলোর সম্ভাবনা আবার ভাগ করে দেয়, কিন্তু model ভবিষ্যতের mask জানে না। ধরুন model পুরো উত্তর হিসেবে "১,৫৫০" লেখার পথে যাচ্ছিল। প্রথম ধাপেই উদ্ধৃতিচিহ্ন আর বাংলা অঙ্ক বাদ পড়ল; বাকি অঙ্কগুলোর মধ্যে সে যা বাছবে, তা তার “আসল” অনুমানের সঙ্গে না-ও মিলতে পারে। আমাদের §৬-এর stepper-এ অনুমোদিত token-গুলোর মোট সম্ভাবনা mask-এর আগে কত ছিল, তা দেখেছেন; সেটি যত কম, model তত বেশি নিজের পছন্দের বাইরে ঠেলা খায়।
দ্বিতীয় সমস্যা token-এর সীমানা। একটি শব্দ কীভাবে token-এ ভাঙবে, model training-এ একটি সাধারণ ধরন শিখেছে। Mask অনেক সময় অস্বাভাবিক ভাঙন বাধ্য করে, যা model-এর কাছে অচেনা। Library-গুলো এ জন্য token healing-এর মতো কৌশল রাখে, তবু পুরো সমাধান নয়।
তৃতীয়, চিন্তার জায়গা। Tam ও সহকর্মীদের ২০২৪ সালের “Let Me Speak Freely?” প্রবন্ধে কিছু reasoning কাজে কড়া format চাপালে নির্ভুলতা কমার ফল পাওয়া যায়; অন্য দলের পরীক্ষায় ফল ভিন্নও এসেছে, তাই বিষয়টি বিতর্কিত। ব্যবহারিক উপায়: schema-তে আগে একটি ছোট ব্যাখ্যার ঘর, পরে উত্তর; বা দুই ধাপে, প্রথমে মুক্ত উত্তর, তারপর আলাদা অনুরোধে JSON. Anthropic-এর নথি অবশ্য ধাপে ধাপে চিন্তার ঘর চাইলে refusal-এর সম্ভাবনার কথা বলে, তাই সেখানে ছোট ব্যাখ্যা চাইতে বলে।
তবে সাধারণ extraction-এ, যেখানে উত্তর ছোট আর schema সরল, mask সাধারণত লাভই দেয়। সিদ্ধান্ত মেপে নিতে হয়, নিজের eval set-এ।
মূল উপলব্ধি: Mask আকার নিশ্চিত করে, মান নয়। লাভ-ক্ষতি নিজের task-এ মাপুন।
প্র ০৩Function calling, JSON mode আর structured output কি একই জিনিসের তিন নাম?
মূল কৌশল এক, উদ্দেশ্য আলাদা, আর ইতিহাসে এগুলো ধাপে ধাপে এসেছে। OpenAI-এর ঘোষণা অনুযায়ী function calling আসে ২০২৩ সালের জুনে: developer function-এর নাম আর parameter-এর JSON Schema দেন, model উত্তর না লিখে argument-এর JSON লেখে। শুরুতে এই JSON সবসময় schema মানত না। একই বছরের নভেম্বরে JSON mode আসে, যা শুধু বৈধ JSON নিশ্চিত করে। ২০২৪ সালের আগস্টে Structured Outputs: schema মানার নিশ্চয়তা, response আর function দুই জায়গাতেই। পরে অন্য কোম্পানিগুলো নিজেদের নামে একই ধারণা যোগ করেছে, আর §৩-এর সারণি দেখায়, আজও সবার কাছে তিনটি স্তর নেই।
পার্থক্যটি কাজের দিক থেকে ভাবুন। Structured output: “উত্তরটিই এই আকারে দাও”, যেমন অর্ডারের ঘর। Function calling: “উত্তরের আগে আমাকে দিয়ে একটি কাজ করাও”, যেমন গুদামে stock দেখা; model সিদ্ধান্ত নেয় কোন function ডাকবে, আর আপনি চালিয়ে ফল ফেরত দেন। Gemini-র নথিও এই ভাগটি এভাবেই করে। JSON mode এর মাঝামাঝি: আকার JSON, কিন্তু নিয়ম আপনার prompt-এ।
তিনটিতেই একটি জিনিস একই থাকে: শেষে আপনার হাতে একটি JSON, যা model লিখেছে। Function-এর argument-এ ভুল জেলা বা ভুল দাম থাকলে ক্ষতি আরও বড়, কারণ সেটি সরাসরি কোনো কাজ চালায়। তাই tool-এর argument-এর যাচাই আরও কড়া হওয়া উচিত, পর্ব ৭১-এ এবং নিরাপত্তার দিক পর্ব ৭৩ · LLM নিরাপত্তায়।
মূল উপলব্ধি: একই decoding-কৌশল তিন উদ্দেশ্যে: আকার, আকার আর নিয়ম, আর কাজের অনুরোধ। যাচাই তিনটিতেই আপনার।
প্র ০৪Prompt-এ schema, few-shot উদাহরণ, নাকি constrained decoding: বাজারঘর কোনটি বাছবে?
উত্তর নির্ভর করে কোথায় model চলছে আর কোন ভুল সবচেয়ে দামি। Hosted API-তে schema-বাঁধা output থাকলে সেটি সবচেয়ে সস্তা উপায়, কারণ format-এর ভুল প্রায় শূন্যে নামে আর retry কম লাগে। নিজের server-এ open model চালালে lm-format-enforcer, outlines বা llama.cpp-এর grammar-এর মতো উপায় আছে; আমাদের run-এ বৈধতা বাড়লেও প্রতি অর্ডারে সময় বেড়েছে (§৭), কারণ প্রতি ধাপে mask হিসাব হয়।
Few-shot অন্য কাজ করে: model দেখে, “Sylhet” লেখা এলেও উত্তরে “সিলেট”, আর দাম ইংরেজি অঙ্কে। পর্ব ৫৬-এ দেখেছেন, SFT-এ model অনেকটাই format শেখে; উদাহরণ সেই format-কে prompt-এর ভেতরেই দেখায়। খরচ হলো লম্বা prompt: প্রতিটি উদাহরণ প্রতিটি অনুরোধে token যোগ করে (পর্ব ৬১ · Few-shot prompting-এ বিস্তারিত)। আমাদের run-এ few-shot-এ সময়ও বেড়েছে।
তাই বাস্তব নকশা প্রায়ই মিশ্র: schema-বাঁধা output বা mask দিয়ে আকার, এক-দুটি উদাহরণ দিয়ে অর্থের ধরন (জেলার বানান, দামের অঙ্ক), নিজের normalisation দিয়ে শেষ পরিষ্কার, আর validator দিয়ে পাহারা। কোনটি কতটা লাগবে, তা আন্দাজে নয়, বাজারঘরের নিজের ৩০০–৫০০টি অর্ডারের eval set-এ মেপে ঠিক করুন; ৩০টিতে CI এত চওড়া যে ছোট পার্থক্য দেখা যায় না।
মূল উপলব্ধি: আকারের জন্য decoding-এর বাঁধন, অর্থের জন্য উদাহরণ আর normalisation, আর দুটোর পরে যাচাই। মিশ্রণটি মেপে বাছুন।
প্র ০৫Model নিজেই কেন “১,৫৫০”-কে 1550 লিখবে না? আর লিখলেও কি নিজের normalisation লাগবে?
বড় model প্রায়ই লেখে, ছোট model প্রায়ই লেখে না, আর দুই ক্ষেত্রেই নিশ্চয়তা নেই। Model লেখাটি token হিসেবে দেখে; “১,৫৫০” তার কাছে কয়েকটি byte-টুকরো, সংখ্যা নয় (পর্ব ৪০)। ইংরেজি অঙ্কে রূপান্তর মানে ভাষা বদলানো, যা training ডেটায় বাংলার কম উপস্থিতির কারণে ছোট model-এর জন্য কঠিন; পর্ব ৫৮-এ একই প্রশ্ন বাংলায় আর ইংরেজিতে বড় ফারাক দেখেছেন।
Model ঠিক লিখলেও নিজের normalisation রাখার তিনটি কারণ। এক, পুনরুৎপাদনযোগ্যতা: একই লেখায় নিজের function সবসময় একই ফল দেয়, model-এর version বদলালেও। দুই, পরীক্ষাযোগ্যতা: function-এর test লেখা যায়, “৯৯.৫০” বা “দেড় হাজার”-এর মতো কোণার ক্ষেত্রসহ। তিন, জবাবদিহি: ভুল দাম গেলে কোন নিয়মে গেল, তা দেখানো যায়।
একটি সূক্ষ্ম দিক Unicode. বাংলায় “য়” বা “ড়” দুইভাবে লেখা যায়, একটি code point বা দুটি মিলে; চোখে এক, string-এ আলাদা। তাই তুলনার আগে দুই পাশে NFC. আরেকটি দিক নীতির: যা বোঝা যায় না, তা আন্দাজে ভরা নয়। “দাম জানি না” থেকে 0 বানালে গুদামের হিসাবে ভুল ঢোকে; None দিয়ে মানুষের কাছে পাঠানোই নিরাপদ। শেষে গোপনীয়তা: অর্ডারের লেখায় ফোন নম্বর থাকতে পারে, কিন্তু schema-তে ঘর নেই, তাই তা ডেটায় যায় না।
মূল উপলব্ধি: Model-এর রূপান্তর সুবিধা, চুক্তি নয়। লিখিত, পরীক্ষিত normalisation-ই ডেটার মান ঠিক রাখে।
অনুশীলন
খাতা-কলমে করুন, তারপর উত্তর মেলান। বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের হিসাব কাজে লাগে।
-
Retry আর mask-এর হিসাব: (ক) একটি prompt-এ প্রতিটি চেষ্টায় schema-বৈধ উত্তর আসার সম্ভাবনা ০.৭, চেষ্টাগুলো স্বাধীন ধরে নিন। সর্বোচ্চ দুবার retry (মোট তিন চেষ্টা) দিলে শেষে বৈধ পাওয়ার সম্ভাবনা কত, আর গড়ে কতবার model ডাকতে হবে? (খ) এক ধাপে চারটি token-এর logit:
"৩.০,1২.০,১২.৫,2১.০. Schema অনুযায়ী শুধু ইংরেজি অঙ্ক চলবে। Mask-এর পরে1-এর সম্ভাবনা কত? (e1 ≈ ২.৭১৮)(ক) তিনবারই ব্যর্থ হওয়ার সম্ভাবনা ০.৩3 = ০.০২৭, তাই বৈধ পাওয়ার সম্ভাবনা ০.৯৭৩. প্রথম ডাক সবসময়; দ্বিতীয় ডাক লাগে ০.৩ সম্ভাবনায়, তৃতীয় ০.০৯-এ। গড় ডাক = ১ + ০.৩ + ০.০৯ = ১.৩৯. আসলে চেষ্টাগুলো স্বাধীন নয়: একই অর্ডারে model বারবার একই ভুল করতে পারে, তাই বাস্তবে লাভ সাধারণত কম, যা §৮-এ মেপেছেন।
(খ) অনুমোদিত শুধু
1আর2. Softmax শুধু এ দুটির ওপর: p(1) = e2 ÷ (e2 + e1) = ১ ÷ (১ + e−1) = ১ ÷ (১ + ০.৩৬৮) ≈ ০.৭৩১. বাকি দুটি token-এর logit বেশি হলেও −∞ হয়ে বাদ। -
আউটপুট অনুমান করুন: কোড না চালিয়ে আউটপুট লিখুন।
import json for s in ['{"q": 2}', '{"q": true}', "{'q': 2}", '{"q": 2,}', '{"q": "২"}']: try: v = json.loads(s)["q"] print(s, "→", type(v).__name__, isinstance(v, int)) except json.JSONDecodeError: print(s, "→ JSON নয়"){"q": 2} → int True {"q": true} → bool True {'q': 2} → JSON নয় {"q": 2,} → JSON নয় {"q": "২"} → str FalseJSON-এ লেখা দুই উদ্ধৃতিচিহ্নেই (
"), এক উদ্ধৃতি Python-এর অভ্যাস, JSON-এ অচল। শেষের বাড়তি কমাটিও অচল।truePython-এTrueহয়, আরboolহলোint-এর subclass, তাইisinstanceTrue বলে। Validator-এ bool আগে আলাদা করতে হয়। -
Schema লিখুন: বাজারঘরের রিভিউ শ্রেণিকরণের জন্য একটি JSON Schema লিখুন:
sentimentতিনটির একটি (positive, negative, neutral),aspectএকটি বা একাধিক (delivery, price, quality, packaging) এর তালিকা, কমপক্ষে একটি আর পুনরাবৃত্তি ছাড়া,confidence০ থেকে ১-এর সংখ্যা, আর বাড়তি কোনো ঘর নয়। তারপর বলুন,{"sentiment": "Positive", "aspect": [], "confidence": 1.2}কোন কোন নিয়ম ভাঙে।{"type": "object", "properties": { "sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]}, "aspect": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "enum": ["delivery", "price", "quality", "packaging"]}}, "confidence": {"type": "number", "minimum": 0, "maximum": 1}}, "required": ["sentiment", "aspect", "confidence"], "additionalProperties": false}উদাহরণটি তিনটি নিয়ম ভাঙে: “Positive” enum-এ নেই (বড় হাতের P; Anthropic-এর নথি সতর্ক করে enum-এর বড়-ছোট হাত নিশ্চিত নয়, তাই তুলনার আগে ছোট হাতে আনা যায়); খালি তালিকা
minItemsভাঙে; ১.২maximumভাঙে। মনে রাখুন, কিছু API এই schema-রuniqueItemsবাminItemsdecoding-এ মানায় না (§৩), তাই নিজের যাচাই লাগবেই। আর model-এর নিজের দেওয়া confidence calibrated নয় (পর্ব ৫২)। -
নকশা সমালোচনা করুন: একটি দল প্রস্তাব দিল: “ছোট model, prompt-এ ‘শুধু JSON দাও’। উত্তর থেকে regex দিয়ে প্রথম
{…}তুলেeval()চালাব। কাজ না করলে যতক্ষণ না হয় আবার জিজ্ঞাসা। দামের কমা মুছেint(). Schema-তে ক্রেতার নাম আর ফোনও রাখব, পরে কাজে লাগতে পারে।” অন্তত পাঁচটি সমস্যা আর সমাধান বলুন।eval(): model-এর লেখা কোড হিসেবে চলে; prompt injection থাকলে বিপদ। সমাধান: শুধুjson.loads.- সীমাহীন retry: একই ভুলে খরচ আর সময় বাড়তেই থাকে। সমাধান: সর্বোচ্চ ২–৩ বার, ভুলের বার্তাসহ, তারপর মানুষ।
- Schema যাচাই নেই: parse হলেই গ্রহণ। সমাধান: jsonschema বা strict Pydantic, তারপর ব্যবসার নিয়ম।
- কমা মুছে
int(): “৯৯.৫০” বা “দেড় হাজার”-এ error বা চুপচাপ ভুল। সমাধান: লিখিত normalisation, test, না বুঝলে None. - নাম আর ফোন: অকারণে ব্যক্তিগত তথ্য জমে। সমাধান: শুধু দরকারি ঘর,
additionalProperties: false. - মাপ নেই: কাজ করে কি না, কেউ জানে না। সমাধান: eval set-এ valid-JSON হার আর field accuracy, CI-সহ; সম্ভব হলে schema-বাঁধা output বা mask.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমLLM-এর JSON উত্তর গ্রহণের নিরাপদ ধাপগুলো সাজান (▲▼ চেপে সরান)
- উত্তর JSON হিসেবে parse করা
- JSON Schema দিয়ে চাবি, ধরন আর সীমা যাচাই
- ব্যবসার নিয়ম মেলানো, যেমন দামের সাধারণ সীমা
- ব্যর্থ হলে সস্তা মেরামত, নইলে ভুলের বার্তাসহ সীমিত বার আবার জিজ্ঞাসা
- তারপরও ব্যর্থ হলে মানুষের কাছে পাঠানো
একটি API-র “JSON mode” (json_object) চালু করলে সাধারণত কোনটি নিশ্চিত হয়?
- উত্তর আপনার schema-র সব চাবি আর ধরন মানবে
- উত্তর বৈধ JSON হবে, কিন্তু কোন চাবি থাকবে তা নয়
- উত্তরের মানগুলো তথ্য হিসেবে সঠিক হবে
- Prompt-এ আর কোনো নির্দেশ লাগবে না
Schema: {"type": "object", "properties": {"qty": {"type": "integer", "minimum": 1}}, "required": ["qty"], "additionalProperties": false}. কোন JSON বৈধ?
{"qty": "2"}{"qty": 0}{"qty": 3}{"qty": 3, "note": "দ্রুত"}
Constrained decoding-এর এক ধাপে কী করা হয়?
- এখন পর্যন্ত লেখার পরে ব্যাকরণে অচল token-এর logit −∞ করা
- Model-এর ওজন schema অনুযায়ী বদলানো
- পুরো উত্তর লেখার পরে schema দিয়ে পরীক্ষা করা
- Temperature শূন্য করা
Python-এ isinstance(v, int) দিয়ে JSON-এর integer ঘর যাচাই করলে কোন ভুল ঘটতে পারে?
- ২.০ integer হিসেবে পার পাবে
- বাংলা “২” লেখা integer হিসেবে পার পাবে
nullinteger হিসেবে পার পাবেtrueinteger হিসেবে পার পাবে
৩০টি অর্ডারে একটি পদ্ধতিতে schema-বৈধ উত্তর ১৮টি, আরেকটিতে ২১টি, একই অর্ডারে। সবচেয়ে যুক্তিসঙ্গত সিদ্ধান্ত কোনটি?
- দ্বিতীয়টি পরিষ্কারভাবে ভালো, এখনই বদলানো উচিত
- একই অর্ডারে জোড়া পার্থক্যের CI দেখা; শূন্য ছুঁলে আরও অর্ডারে মাপা
- প্রথমটি ভালো, কারণ কম উত্তর বৈধ মানে কড়া
- বৈধতার সংখ্যা অর্থহীন, শুধু field accuracy দেখা
“৳১,২৫,০০০” লেখাটি integer-এ আনার সঠিক ফল কোনটি?
- 1
- 125
- 125000
- 1250000
একটি API-র নথি বলে, schema-বাঁধা output-এ minimum আর maximum সমর্থিত নয়। দামের সীমা ১ থেকে ২,০০,০০০ টাকা রাখতে কী করবেন?
- Schema থেকে সীমাটি সরিয়ে বর্ণনায় লেখা, আর নিজের validator-এ পরীক্ষা
- Schema-তে রেখে দেওয়া; model বুঝে নেবে
- শুধু prompt-এ অনুরোধ করা, যাচাই ছাড়া
- price_taka-কে string করে দেওয়া
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- যন্ত্রের জন্য উত্তর চাই নির্দিষ্ট আকারে: JSON, আর নিয়ম JSON Schema-তে; শুধু দরকারি ঘর,
additionalProperties: false. - তিন স্তর: prompt, JSON mode, schema-বাঁধা output বা strict tool. কোন API কোন keyword মানায়, তা তারিখসহ নথিতে দেখুন।
- যাচাই তিন ধাপে: parse, schema, ব্যবসার নিয়ম। Schema পার মানেই সঠিক নয়; bool-কে integer ভাবার ফাঁদ মনে রাখুন।
- বাংলা অঙ্ক, দাম আর নাম লিখিত, পরীক্ষিত নিয়মে এক রূপে আনুন; না বুঝলে None.
- Constrained decoding প্রতি ধাপে অচল token-এর logit −∞ করে আকার নিশ্চিত করে, মান নয়। Repair-retry সীমিত রাখুন, আর সব পদ্ধতি eval set-এ CI-সহ মাপুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৬৩ · Reasoning prompt — chain-of-thought, self-consistency আর ধাপে ধাপে চিন্তাপরবর্তী পর্বধাপে ধাপে চিন্তা কখন কাজে দেয়, আর তার token-এর দাম।
- ডিপ লার্নিং, NLP আর Generative AI · কোর্স হোম৯১টি পর্বপুরো কোর্সের রোডম্যাপ; পরের পর্ব ৬৩ · Reasoning prompt.
- পর্ব ৫০ · Decodingফিরে দেখুনLogit থেকে token বাছাই, যেখানে mask বসে।
- পর্ব ৫৮ · LLM মূল্যায়নফিরে দেখুনবাংলায় normalisation, bootstrap CI আর জোড়া তুলনা।
- ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক · পর্ব ২৭ · Request যাচাইপাশের ট্র্যাকZod দিয়ে schema আর নিরাপদ ইনপুট, একই চিন্তা ওয়েবে।
- ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক · পর্ব ৬৯ · FastAPIপাশের ট্র্যাকPydantic আর স্বয়ংক্রিয় docs.