ডেভেলপাররা এখন একটি সাধারণ পাইথন লুপের মাধ্যমে লার্জ ল্যাঙ্গুয়েজ মডেল (LLMs) থেকে আসা ত্রুটিপূর্ণ JSON ঠিক করার খরচ নিয়ন্ত্রণে রাখতে পারেন, যেখানে মেরামতের প্রচেষ্টার সংখ্যা সর্বোচ্চ দুটিতে সীমাবদ্ধ রাখা হয়। এই প্যাটার্নটি—জেনারেট করা, পার্স করা, ভ্যালিডেট করা, পুনরায় চেষ্টা করা, এবং তারপর গ্রহণ করা বা ব্যর্থ হওয়া—একটি "সম্ভাব্য-সঠিক JSON"-কে একটি পরিমাপযোগ্য সাফল্যের হারে রূপান্তরিত করে এবং টোকেন ব্যবহারের অনিয়ন্ত্রিত বৃদ্ধি রোধ করে।

কেন একটি রিপেয়ার লুপ গুরুত্বপূর্ণ

LLM-গুলোকে প্রায়ই "JSON রিটার্ন করতে" নির্দেশ দেওয়া হয়, তবুও আউটপুটে প্রায়ই সিনট্যাক্স ত্রুটি, মিসিং কী (missing keys), বা এমন ভ্যালু থাকে যা বিজনেস রুলস ভঙ্গ করে। একটি ডাউনস্ট্রিম সিস্টেম যা একটি কঠোর পেলোড (strict payload) আশা করে, তাকে যদি এমন আউটপুট দেওয়া হয় তবে সেটি ক্র্যাশ করতে পারে বা ভুল ফলাফল দিতে পারে। এই সমস্যাগুলো শনাক্ত ও সংশোধনের কোনো পদ্ধতিগত উপায় না থাকলে, টিমগুলো হয় উচ্চ ব্যর্থতার হার মেনে নেয় অথবা মডেলটি সঠিক উত্তর না দেওয়া পর্যন্ত বারবার প্রম্পট দিয়ে টাকা অপচয় করে।

তিনটি ভ্যালিডেশন লেয়ার

একটি নির্ভরযোগ্য লুপ ভ্যালিডেশনকে তিনটি ভাগে ভাগ করে:

  • Syntax – স্ট্রিংটি কি আদৌ JSON হিসেবে পার্স করা যাচ্ছে?
  • Shape – টপ-লেভেল অবজেক্টটিতে কি ঠিক প্রত্যাশিত কী (keys) গুলো আছে?
  • Semantic – ভ্যালুগুলো কি ডোমেইন সীমাবদ্ধতা মেনে চলছে (উদাহরণস্বরূপ, একটি "category" অবশ্যই একটি পূর্বনির্ধারিত সেটের অন্তর্ভুক্ত হতে হবে)?

প্রথম ব্যর্থ লেয়ারেই থেমে যাওয়ার মাধ্যমে, লুপটি মডেলকে একটি সুনির্দিষ্ট এরর মেসেজ দিতে পারে, যা পরবর্তী প্রচেষ্টায় সঠিক সমাধানের সম্ভাবনা নাটকীয়ভাবে বাড়িয়ে দেয়।

মিনিমাল পাইথন উদাহরণ

নিচের কোডটি শুধুমাত্র স্ট্যান্ডার্ড লাইব্রেরি ব্যবহার করে। এটি একটি ছোট স্কিমা (একটি ডিকশনারি যার কী হলো category এবং summary) এবং অনুমোদিত ক্যাটাগরির একটি নির্দিষ্ট তালিকা সংজ্ঞায়িত করে।

import json

CATEGORIES = {"bug", "feature", "question"}

def validate(raw: str):
    try:
        value = json.loads(raw)
    except json.JSONDecodeError as exc:
        return None, f"syntax: {exc.msg}"

    if not isinstance(value, dict):
        return None, "shape: root must be an object"
    if set(value) != {"category", "summary"}:
        return None, "shape: expected category and summary"
    if value["category"] not in CATEGORIES:
        return None, "semantic: invalid category"
    if not isinstance(value["summary"], str) or not value["summary"].strip():
        return None, "semantic: summary must be text"

    return value, None

রিপেয়ার লুপটি নিজেই প্রচেষ্টার একটি কঠোর সীমা নির্ধারণ করে দেয়:

MAX_ATTEMPTS = 2
raw = model_response   # initial LLM output

for attempt in range(MAX_ATTEMPTS + 1):
    value, error = validate(raw)
    if error is None:
        break
    if attempt == MAX_ATTEMPTS:
        raise RuntimeError(f"failed after {MAX_ATTEMPTS} retries: {error}")

    # Send the error and schema back to the model for a fix
    raw = ask_model_to_repair(raw, error)

যদি JSON প্রথমবার ভ্যালিডেশন পাস করে, তবে লুপটি সাথে সাথে শেষ হয়ে যায়। অন্যথায় এটি পুনরায় চেষ্টা করে এবং মডেলকে একটি সংক্ষিপ্ত পেলোড প্রদান করে: মূল আউটপুট, সঠিক এরর স্ট্রিং এবং প্রত্যাশিত স্কিমা। দুটি ব্যর্থ প্রচেষ্টার পর প্রক্রিয়াটি একটি স্পষ্ট এক্সেপশন (exception) সহ বন্ধ হয়ে যায়।

সাশ্রয়ী মেরামতের জন্য দুটি নিয়ম

১. রিপেয়ার প্রম্পটগুলো সংক্ষিপ্ত রাখুন – শুধুমাত্র র (raw) JSON, এরর মেসেজ এবং স্কিমা অন্তর্ভুক্ত করুন। দীর্ঘ চ্যাট হিস্ট্রি যোগ করলে টোকেন ব্যবহার বেড়ে যায় এবং মডেলকে বিভ্রান্ত করতে পারে, যা নির্ভুলতা না বাড়িয়ে প্রতিবার প্রচেষ্টার খরচ বাড়িয়ে দেয়।

২. ভ্যালিডেশন এবং ফ্যাক্ট-চেকিং আলাদা রাখুন – পার্সার কাঠামোগত সমস্যা শনাক্ত করতে পারে, কিন্তু একটি "summary" স্টেটমেন্ট সত্য কি না তা যাচাই করতে পারে না। ফ্যাক্ট-চেকিং পরবর্তী পর্যায়ের কাজ; একটি মিথ্যা দাবিকে "মেরামত" করার চেষ্টা কেবল মিথ্যাটিকে আরও পরিচ্ছন্ন করে তোলে।

সাফল্য পরিমাপ করা

প্রতিটি প্রচেষ্টার এরর টাইপ (syntax, shape, semantic) এবং লুপটি সফল হয়েছে কি না তা লগ করা একটি সুনির্দিষ্ট মেট্রিক প্রদান করে: নির্দিষ্ট সংখ্যক প্রচেষ্টার মধ্যে LLM রেসপন্সগুলোর কত শতাংশ ব্যবহারযোগ্য হয়ে উঠেছে। টিমগুলো সময়ের সাথে এটি ট্র্যাক করতে পারে, প্রম্পট স্টাইল তুলনা করতে পারে বা বিভিন্ন স্কিমা সংজ্ঞার সাথে পরীক্ষা করতে পারে।

সম্ভাব্য অসুবিধা

লুপের সীমা নির্ধারণ করার অর্থ হলো কিছু ত্রুটিপূর্ণ আউটপুট সীমা অতিক্রম করার পর বাতিল হয়ে যাবে, যা মডেলের গুণমান কম হলে সামগ্রিক ব্যর্থতার হার বাড়িয়ে দিতে পারে। যেসব পরিবেশে প্রতিটি রেসপন্স অত্যন্ত গুরুত্বপূর্ণ, সেখানে ডেভেলপাররা অতিরিক্ত টোকেনের বিনিময়ে উচ্চতর রিট্রাই লিমিট পছন্দ করতে পারেন। এখানে একটি স্পষ্ট ট্রেড-অফ রয়েছে: উচ্চ কভারেজের জন্য বেশি খরচ, অথবা একটি পূর্বনির্ধারিত সীমার সাথে সাশ্রয়ী বাজেট।

পরবর্তীতে যা খেয়াল রাখা উচিত

  • মেরামতের জন্য প্রম্পট ইঞ্জিনিয়ারিং – এরর মেসেজ ফরম্যাটটি উন্নত করলে প্রয়োজনীয় রিট্রাইয়ের সংখ্যা কমিয়ে আনা সম্ভব।
  • বিকল্প পার্সার – কিছু লাইব্রেরি টলারেন্ট পার্সিং (tolerant parsing) প্রদান করে যা ছোটখাটো সমস্যা স্বয়ংক্রিয়ভাবে সংশোধন করতে পারে; একটি সীমাবদ্ধ লুপের তুলনায় সেগুলোর খরচ তুলনা করা ফলপ্রসূ হতে পারে।
  • মডেল আপডেট – নতুন LLM সংস্করণগুলো সরাসরি আরও পরিচ্ছন্ন JSON তৈরি করতে পারে, যা রিট্রাই লিমিট আরও কমিয়ে আনার সুযোগ তৈরি করতে পারে।

একটি সীমাবদ্ধ JSON রিপেয়ার লুপ ডেভেলপারদের একটি ব্যবহারিক টুল প্রদান করে যা অনিয়ন্ত্রিত খরচ ছাড়াই অস্পষ্ট LLM আউটপুটকে নির্ভরযোগ্য ডেটাতে রূপান্তরিত করতে পারে। ভ্যালিডেশনকে একটি গুরুত্বপূর্ণ ধাপ হিসেবে বিবেচনা করে এবং রিট্রাইয়ের সংখ্যা সীমিত করার মাধ্যমে, টিমগুলো তাদের বাজেট এবং ডাউনস্ট্রিম সিস্টেম—উভয়কেই সন্তুষ্ট রাখতে পারে।